

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/hello_lixi.gitee.io/img/fluid.png">
  <link rel="icon" href="/hello_lixi.gitee.io/img/fluid.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="Mr.LI">
  <meta name="keywords" content="">
  
    <meta name="description" content="Redis的安装、基本数据类型原理和使用方法、持久化、SpringBoot整合Redis开发、分布式锁、缓存穿透和雪崩以及Redis的主从复制等内容">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis学习">
<meta property="og:url" content="https://xistudycode.gitee.io/hello_lixi.gitee.io/2023/04/11/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/index.html">
<meta property="og:site_name" content="Welcome to the technology blog">
<meta property="og:description" content="Redis的安装、基本数据类型原理和使用方法、持久化、SpringBoot整合Redis开发、分布式锁、缓存穿透和雪崩以及Redis的主从复制等内容">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230220112239429.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230220112345664.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223182633336.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/9ae1ec7ce211483aacd0611540a076bb.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230221161015421.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230221161126363.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-6a84f8a661df7996fbcd4ca65c1a40b6_720w.webp">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-8fd5104994d92443af483d747d631390_720w.webp">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/99e1254f32444e6a87a891ff643baa26.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-102238a40860c08faf37c492f61f6d94_720w.webp">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/bce9ce6e7dccd0edc378720ca82c465d.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230301171709024.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211212140800078-2065414849.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211212171405687-941010289.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211213175231490-789691828.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230222101213264.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/af0600c3d67d4f1aa515367036ad82bf.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223113531435.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223151012217.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223145617900.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302144348559.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302144621078.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302145451720.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302145615776.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105126253.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105251166.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105333747.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105404896.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303102957857.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303103730514.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-d18f48c7c7ae783f042dd7b00ed6be99_r.jpg">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E8%BF%87%E7%A8%8B.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E8%BF%9E%E6%8E%A5%E9%98%B6%E6%AE%B5.png">
<meta property="og:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302164349034.png">
<meta property="og:image" content="https://xistudycode.gitee.io/hello_lixi.gitee.io/2023/04/11/Redis%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%9B%BE%E5%BA%93/image-20230302164441950.png">
<meta property="article:published_time" content="2023-04-11T07:08:29.364Z">
<meta property="article:modified_time" content="2023-04-11T07:27:50.236Z">
<meta property="article:author" content="Mr.LI">
<meta property="article:tag" content="Redis">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230220112239429.png">
  
  
    <meta name="referrer" content="no-referrer-when-downgrade">
  
  
  <title>Redis学习 - Welcome to the technology blog</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/hello_lixi.gitee.io/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/hello_lixi.gitee.io/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/hello_lixi.gitee.io/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"xistudycode.gitee.io","root":"/hello_lixi.gitee.io/","version":"1.9.3","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/hello_lixi.gitee.io/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/hello_lixi.gitee.io/js/utils.js" ></script>
  <script  src="/hello_lixi.gitee.io/js/color-schema.js" ></script>
  

  

  

  

  

  

  

  



  
<meta name="generator" content="Hexo 5.4.2"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/hello_lixi.gitee.io/">
      <strong>xBlog</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/hello_lixi.gitee.io/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/hello_lixi.gitee.io/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/hello_lixi.gitee.io/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/hello_lixi.gitee.io/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/hello_lixi.gitee.io/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/hello_lixi.gitee.io/img/default.png') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="Redis学习"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        Mr.LI
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2023-04-11 15:08" pubdate>
          2023年4月11日 下午
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          58k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          481 分钟
        
      </span>
    

    
    
      
        <span id="busuanzi_container_page_pv" style="display: none">
          <i class="iconfont icon-eye" aria-hidden="true"></i>
          <span id="busuanzi_value_page_pv"></span> 次
        </span>
        
      
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">Redis学习</h1>
            
              <p class="note note-info">
                
                  
                    本文最后更新于：2023年4月11日 下午
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <h2 id="1-Redis-简介"><a href="#1-Redis-简介" class="headerlink" title="1. Redis 简介"></a>1. Redis 简介</h2><p>Reids是REmote DIctionary Server远程字典服务的缩写，是一款内存高速缓存数据库。</p>
<p>Redis 是完全开源免费的，是一个高性能的key-value数据库。</p>
<p>Redis 与其他 key - value 缓存产品有以下三个特点：</p>
<ol>
<li><p>Redis支持数据的持久化，可以将内存中的数据保存在磁盘中，重启的时候可以再次加载进行使用。</p>
</li>
<li><p>Redis不仅仅支持简单的key-value类型的数据，同时还提供list，set，zset，hash等数据结构的存储。</p>
</li>
<li><p>Redis支持数据的备份，即master-slave模式的数据备份。</p>
</li>
</ol>
<h3 id="1-1Redis-优势"><a href="#1-1Redis-优势" class="headerlink" title="1.1Redis 优势"></a>1.1Redis 优势</h3><p>性能极高 – Redis能读的速度是110000次/s,   写的速度是81000次/s 。</p>
<p>丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。</p>
<p>原子 – Redis的所有操作都是原子性的，同时Redis还支持对几个操作全并后的原子性执行。</p>
<p>丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性</p>
<h2 id="2-Redis-安装"><a href="#2-Redis-安装" class="headerlink" title="2. Redis 安装"></a>2. Redis 安装</h2><h3 id="2-1-Window-下安装"><a href="#2-1-Window-下安装" class="headerlink" title="2.1 Window 下安装"></a>2.1 Window 下安装</h3><p><strong>下载地址：</strong><a target="_blank" rel="noopener" href="https://github.com/MSOpenTech/redis/releases%E3%80%82">https://github.com/MSOpenTech/redis/releases。</a></p>
<p>Redis 支持 32 位和 64 位。这个需要根据系统平台的实际情况选择，下载 <strong>Redis-x64-xxx.zip</strong>压缩包到 C 盘，解压后，将文件夹重新命名为 <strong>redis</strong>。</p>
<p>打开一个<strong>cmd</strong> 窗口 使用cd命令切换目录到redis的解压目录，运行</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">redis-server.exe redis.windows.conf<br></code></pre></td></tr></table></figure>

<p>输入之后，会显示如下界面：</p>
<img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230220112239429.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230220112239429"  />

<p>这时候另启一个cmd窗口，原来的不要关闭，不然就无法访问服务端了。</p>
<p>切换到redis目录下运行</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">redis-cli.exe -h <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> -p <span class="hljs-number">6379</span><br></code></pre></td></tr></table></figure>

<p>如果在配置文件中设置了密码，则需要进行认证，否则会提示出错；</p>
<p>使用auth进行登录。</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">auth password <br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230220112345664.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230220112345664"></p>
<h3 id="2-2-docker-下安装"><a href="#2-2-docker-下安装" class="headerlink" title="2.2 docker 下安装"></a>2.2 docker 下安装</h3><p>1.docker搜索redis镜像</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker search redis<br></code></pre></td></tr></table></figure>



<p>2.docker拉取redis镜像</p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs txt">命令： docker pull &lt;镜像名称&gt;:&lt;版本号&gt;   #拉取指定版本的redis<br></code></pre></td></tr></table></figure>

<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker pull redis #默认拉取最新版<br></code></pre></td></tr></table></figure>

<p>3.Docker挂载配置文件</p>
<p>接下来就是要将redis 的配置文件进行挂载，以配置文件方式启动redis 容器。（挂载：即将宿主的文件和容器内部目录相关联，相互绑定，在宿主机内修改文件的话也随之修改容器内部文件）</p>
<p>1）、挂载 redis 的配置文件</p>
<p>2）、挂载 redis 的持久化文件（为了数据的持久化）。</p>
<p>本机配置文件是放在</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">win 下redis.conf文件位置： D:<span class="hljs-regexp">/docker/</span>Workspace_docker<span class="hljs-regexp">/redis/</span>conf/redis.conf<br>win 下redis的data文件位置 ： D:<span class="hljs-regexp">/docker/</span>Workspace_docker<span class="hljs-regexp">/redis/</span>data<br></code></pre></td></tr></table></figure>

<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker run --log-opt max-size=<span class="hljs-number">100</span>m --log-opt max-file=<span class="hljs-number">2</span> -p <span class="hljs-number">6379</span>:<span class="hljs-number">6379</span> --name myredis -v /D/docker/Workspace_docker/redis/conf:/etc/redis -v /D/docker/Workspace_docker/redis/data:/data -d redis redis-server /etc/redis/redis.conf  --appendonly yes<br></code></pre></td></tr></table></figure>

<ul>
<li>–restart=always 总是开机启动</li>
<li>–log是日志方面的</li>
<li>-p 6379:6379 将6379端口挂载出去</li>
<li>–name 给这个容器取一个名字</li>
<li>-v 数据卷挂载</li>
</ul>
<p>D:/docker/Workspace_docker/redis/conf/redis.conf:/etc/redis/redis.conf 这里是将 win 路径下的        myredis.conf 和redis下的redis.conf 挂载在一起。</p>
<ul>
<li>D:/docker/Workspace_docker/redis/data:/data 这个同上</li>
<li>-d redis 表示后台启动redis</li>
<li>redis-server /etc/redis/redis.conf 以配置文件启动redis，加载容器内的conf文件，最终找到的是挂载的目录 /etc/redis/redis.conf 也就是liunx下的/home/redis/myredis/myredis.conf</li>
<li>–appendonly yes 开启redis 持久化</li>
<li>–requirepass 000415 设置的密码 </li>
</ul>
<ol start="4">
<li><p>查看启动状态</p>
<p>docker ps -a</p>
</li>
</ol>
<p>5.查看容器的运行日志，此处 <code>--since 30m</code> 是查看此容器30分钟之内的日志情况。</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker logs --since <span class="hljs-number">30</span>m myredis<br></code></pre></td></tr></table></figure>

<p>6.容器内部连接进行测试</p>
<p>命令：docker exec -it &lt;容器名&gt; /bin/bash</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker exec -it myredis redis-cli<br></code></pre></td></tr></table></figure>

<p>7.验证密码</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">auth</span> 密码<br></code></pre></td></tr></table></figure>



<p>删除容器</p>
<p>1.查看所有在运行的容器：</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker ps -a<br></code></pre></td></tr></table></figure>

<p>2.停止运行redis</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker stop myredis #或者id<br></code></pre></td></tr></table></figure>

<p>3.删除redis容器</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">docker rm myredis #id<br></code></pre></td></tr></table></figure>

<h3 id="2-3-ubuntu下安装redis"><a href="#2-3-ubuntu下安装redis" class="headerlink" title="2.3 ubuntu下安装redis"></a>2.3 ubuntu下安装redis</h3><p>apt安装redis</p>
<p>1.更新apt</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">sudo apt update<br></code></pre></td></tr></table></figure>



<p>2.使用apt安装redis</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">sudo apt-get install redis-server<br></code></pre></td></tr></table></figure>



<p>安装完成</p>
<p>3.安装vim编辑器</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">sudo apt-get install vim<br></code></pre></td></tr></table></figure>



<p>4.使用vim编辑redis的配置文件，修改配置，配置信息在    /etc/redis/redis.conf中</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">sudo vim <span class="hljs-regexp">/etc/</span>redis/redis.conf<br></code></pre></td></tr></table></figure>





<h2 id="3-Redis配置解析"><a href="#3-Redis配置解析" class="headerlink" title="3. Redis配置解析"></a>3. Redis配置解析</h2><p>在win系统下，直接修改redis的config文件，即可修改配置信息，</p>
<p>docker由于挂载了宿主机的文件目录，因此在修改宿主机的redis的配置文件之后，重启docker即可，启动的时候就通过配置文件启动的。</p>
<p>ubuntu下，需要修改/etc/redis/redis.conf配置文件信息</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><code class="hljs conf"># Redis配置文件样例<br><br># Note on units: when memory size is needed, it is possible to specifiy<br># it in the usual form of 1k 5GB 4M and so forth:<br>#<br># 1k =&gt; 1000 bytes<br># 1kb =&gt; 1024 bytes<br># 1m =&gt; 1000000 bytes<br># 1mb =&gt; 1024*1024 bytes<br># 1g =&gt; 1000000000 bytes<br># 1gb =&gt; 1024*1024*1024 bytes<br>#<br># units are case insensitive so 1GB 1Gb 1gB are all the same.<br><br># Redis默认不是以守护进程的方式运行，可以通过该配置项修改，使用yes启用守护进程<br># yes时，redis会在后台运行，此时redis将一直运行，除非手动kill该进程。同时将进程pid号写入至redis.conf选项pidfile设置的文件中，默认会生成在/var/run/redis.pid，也可以通过pidfile来指定pid文件生成的位置<br><br>daemonize no<br><br># 指定Redis监听端口，默认端口为6379<br># 如果指定0端口，表示Redis不监听TCP连接<br>port 6379<br><br># 绑定的主机地址<br># 可以绑定单一接口，如果没有绑定，所有接口都会监听到来的连接<br># bind 0.0.0.0，表示所有主机都可以连接到redis<br># bind 127.0.0.1<br><br># 当客户端闲置多长时间后关闭连接，如果指定为0，表示关闭该功能<br>timeout 0<br><br># 指定日志记录级别，Redis总共支持四个级别：debug、verbose、notice、warning，默认为verbose<br># debug (很多信息, 对开发／测试比较有用)<br># verbose (许多很少有用的信息，但不像调试级别那样混乱)<br># notice (适度冗长，可能是您在生产中想要的)<br># warning (只记录非常重要/关键的消息)<br>loglevel debug<br><br># 日志记录方式，默认为标准输出<br><br>logfile stdout<br><br># 设置数据库的数量，默认数据库为0，可以使用select &lt;dbid&gt;命令在连接上指定数据库id<br># dbid是从0到‘databases’-1的数目<br>databases 16<br><br>################################ SNAPSHOTTING  #################################<br># 指定在多长时间内，有多少次更新操作，就将数据同步到数据文件，可以多个条件配合<br># Save the DB on disk:<br>#<br>#   save &lt;seconds&gt; &lt;changes&gt;<br>#<br>#   Will save the DB if both the given number of seconds and the given<br>#   number of write operations against the DB occurred.<br>#<br>#   满足以下条件将会同步数据:<br>#   900秒（15分钟）内有1个更改<br>#   300秒（5分钟）内有10个更改<br>#   60秒内有10000个更改<br>#   Note: 可以把所有“save”行注释掉，这样就取消同步操作了<br><br>save 900 1<br>save 300 10<br>save 60 10000<br><br># 指定存储至本地数据库时是否压缩数据，默认为yes，Redis采用LZF压缩，如果为了节省CPU时间，可以关闭该选项，但会导致数据库文件变的巨大<br>rdbcompression yes<br><br># 指定本地数据库文件名，默认值为dump.rdb<br>dbfilename dump.rdb<br><br># 工作目录.<br># 指定本地数据库存放目录，文件名由上一个dbfilename配置项指定<br># 注意，这里只能指定一个目录，不能指定文件名<br><br>dir ./<br><br>################################## SECURITY ###################################<br><br># 设置Redis连接密码，如果配置了连接密码，客户端在连接Redis时需要通过auth &lt;password&gt;命令提供密码，默认关闭<br># requirepass 123456<br><br>################################### LIMITS ####################################<br><br># 设置同一时间最大客户端连接数，默认无限制，Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数，<br># 如果设置maxclients 0，表示不作限制。当客户端连接数到达限制时，Redis会关闭新的连接并向客户端返回max Number of clients reached错误信息<br><br># maxclients 128<br><br># 指定Redis最大内存限制，Redis在启动时会把数据加载到内存中，达到最大内存后，Redis会先尝试清除已到期或即将到期的Key，<br># 当此方法处理后，仍然到达最大内存设置，将无法再进行写入操作，但仍然可以进行读取操作。<br># Redis新的vm机制，会把Key存放内存，Value会存放在swap区<br><br># maxmemory &lt;bytes&gt;<br><br>############################## APPEND ONLY MODE ###############################<br><br># 指定是否在每次更新操作后进行日志记录，Redis在默认情况下是异步的把数据写入磁盘，如果不开启，可能会在断电时导致一段时间内的数据丢失。<br># 因为redis本身同步数据文件是按上面save条件来同步的，所以有的数据会在一段时间内只存在于内存中。默认为no<br>appendonly no<br><br># 指定更新日志文件名，默认为appendonly.aof<br># appendfilename appendonly.aof<br><br># 指定更新日志条件，共有3个可选值：<br># no:表示等操作系统进行数据缓存同步到磁盘（快）<br># always:表示每次更新操作后手动调用fsync()将数据写到磁盘（慢，安全）<br># everysec:表示每秒同步一次（折中，默认值）<br><br>appendfsync everysec<br># appendfsync no<br><br></code></pre></td></tr></table></figure>



<h2 id="4-Redis-数据类型及常用命令"><a href="#4-Redis-数据类型及常用命令" class="headerlink" title="4. Redis 数据类型及常用命令"></a>4. Redis 数据类型及常用命令</h2><p>redis命令手册      <a target="_blank" rel="noopener" href="https://www.redis.net.cn/order/">https://www.redis.net.cn/order/</a></p>
<h3 id="4-1-redis基本命令"><a href="#4-1-redis基本命令" class="headerlink" title="4.1 redis基本命令"></a>4.1 redis基本命令</h3><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs txt">连接服务端：<br>    ./redis-cli -h 127.0.0.1 -p 6379<br>Redis默认是有16个数据库的（0~15）通过select命令来切换数据库<br>    select 1    -- 连接到第 2 个数据库 0开始计算<br>往数据库设置string类型值<br>    set name zhangsan<br>查看数据库中key的数量<br>    dbsize<br>查看刚才添加的key的值<br>    get name<br>查看所有key的值<br>    keys *<br>清空全部数据库和清空当前库<br>    flushall（清空全部库） flushdb（清空当前库）  <br>删除添加的name key键<br>    del name<br></code></pre></td></tr></table></figure>

<h3 id="4-2-key值命令"><a href="#4-2-key值命令" class="headerlink" title="4.2 key值命令"></a>4.2 key值命令</h3><p>key值命令可以说是一些类型的公共命令，比如有设置定时时间，排序，数据迁移等等</p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：keys pattern<br>说明：用来匹配和查看指定的key<br>    pattern：查询条件<br>        h?llo       匹配 hello, hallo 和 hxllo<br>        h*llo       匹配 hllo 和 heeeello<br>        h[ae]llo    匹配 hello 和 hallo, 不匹配如 hillo<br>        h[^e]llo    匹配 hallo, hbllo, ... 不匹配如 hello<br>        h[a-e]llo   匹配 hallo 和 hbllo, [a-e]说明是a~e这个范围 ，如hcllo也可以匹配<br>        若想匹配如转义字符的如下,就需要使用 \ 转义你想匹配的特殊字符。<br>        set na\me zhangsan<br>        keys na[\\]me<br><br>语法：del key [key ...]<br>说明：删除指定的key<br>    del name age address<br><br>语法：unlink key [key ...]<br>说明：其实这个和删除del命令很像，也是存在key删除，不存在则忽略；删除几个键值，则返回删除的个数<br>    unlink name1 name2 name3<br>    注：del和unlink区别<br>    del：它是线程阻塞的，当执行del命令是，del在没执行完时，其它后续的命令是无法进入的（要安全就使用del）<br>    unlink:它不是线程阻塞的，当执行unlink命令时，它会将要删除的键移交给另外线程，然后将当前要删除的键与数据库空间断开连接，后续则由其它线程异步删除这些键（要效率快就使用unlink）<br><br>语法：exists key [key ...]<br>说明：返回要查询的key是否存在，存在则返回1，如果设置四个key都存在则会返回4；返回0则代表没有<br>    exists name -- 查看是否存在name的key<br>    exists name name  -- 重复写两次name ，如果name存在则返回2<br>    exists name address -- 查看当前是否存在name和address键<br>    注：exists后面不管携带单个，多个或者有重复的，最终是存在一个就累加1<br><br>语法：type key<br>说明：返回当前指定的key的类型。可返回的类型是: string,list,set,zset,hash和stream<br><br>语法：rename key newkey<br>说明：修改key名称，存在原来则覆盖，不存在则抛错；如果修改key1为key2，key2存在，则key1覆盖key2的值<br>    rename name name1<br>    <br>语法：renamenx key newkey<br>说明：修改key名称存在则覆盖，不存在则抛错；如果修改key1为key2，key2存在，则key1修改不成功<br><br>语法：randomkey<br>说明：随机返回一个key名称<br><br>语法：copy source destination [db destination-db] [replace]<br>说明：拷贝当前某一个key的值，存放到新的key中（可以跨库拷贝）返回 1 成功 0 失败<br>    copy name1 name2  -- 把 name1 的值 拷贝到 name2 里<br>    copy name1 name2 db 5 -- 把 name1 的值拷贝到第6号数据库name2里<br>    copy name1 name2 replace -- 把 name1 的值拷贝到name2里，存在则强行覆盖<br><br>语法：expire key seconds [nx|xx|gt|lt]<br>    为一个存在的key设置过期时间 秒<br>语法：pexpire key milliseconds [nx|xx|gt|lt]<br>    为一个存在的key设置过期时间 毫秒<br>语法：expireat key timestamp [nx|xx|gt|lt]<br>    为一个存在的key设置过期时间 格式是uinx时间戳并精确到秒<br>语法：pexpireat key milliseconds-timestamp [nx|xx|gt|lt]<br>    为一个存在的key设置过期时间 格式是uinx时间戳并精确到毫秒<br>说明：先设置一个key，并指定过期时间 秒/毫秒/时间戳秒/时间戳毫秒；返回 1 成功 0 失败<br>    expire name 300 -- 把name键设置300秒过期<br>    pexpire name 3000 -- 把name键设置3000毫秒过期（3秒）<br>    expireat name 1633190400 -- 把name键设置为2021-10-2 00:00:00到期(精确秒)<br>    pexpireat name 1633190400000 -- 把name键设置为2021-10-2 00:00:00到期(精确毫秒)<br>    注:使用del可以删除定时的key<br>       使用set可以覆盖定时的key；<br>       使用getset可以返回并设置值，并会删除定时<br>       如使用rename修改key名称，那么key定时器会被携带不会被删除<br><br>语法：persist key<br>说明：清除当前有定时时间的键值，设置永不过期（和普通键值一样了），关闭后并不会删除已有的键值<br>    persist name    -- 关闭存在定时的键值<br><br>语法：ttl key<br>说明：查看当前有定时key的剩余时间，返回秒<br>    <br>语法：pttl key<br>说明：查看当前有定时key的剩余时间，返回毫秒<br>    ttl name<br>    pttl name<br>    注：没过期反剩余时间 过期反-2  没设置过期时间的key反-1<br><br>语法：move key db<br>说明：把指定的键值移动到选定的数据库db当中。如果key在目标数据库中已存在，或者key在源数据库中不存，则key不会被移动。<br>    move name 2 -- 把name移动到三号数据库里<br><br></code></pre></td></tr></table></figure>



<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223182633336.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230223182633336"></p>
<h3 id="4-3-String（字符串）类型命令"><a href="#4-3-String（字符串）类型命令" class="headerlink" title="4.3 String（字符串）类型命令"></a>4.3 String（字符串）类型命令</h3><p>String是Redis中最常用的一种数据类型，也是Redis中最简单的一种数据类型。首先，表面上它是字符串，但其实他可以灵活的表示字符串、整数、浮点数3种值，Redis会自动的识别这3种值。由于Redis是使用c语言实现的，而c语言中没有专门的字符串变量，通常是一个字符数组存放一个字符串，然后在字符串的末尾存放一个空字符‘/0’，因此redis就需要自己实现一个类似于String的结构SDS(simple dynamic string)，代码结构：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java">typedef struct sdshdr &#123;<br>    <span class="hljs-comment">// buf中已经占用的字符长度</span><br>    unsigned <span class="hljs-type">int</span> len;<br>    <span class="hljs-comment">// buf中剩余可用的字符长度</span><br>    unsigned <span class="hljs-type">int</span> free;<br>    <span class="hljs-comment">// 数据空间</span><br>    <span class="hljs-type">char</span> buf[];<br>&#125;<br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/9ae1ec7ce211483aacd0611540a076bb.jpg" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>free 属性的值为0，表示这个SDS没有分配任何未使用空间<br>len 属性的值为7，表示这个SDS保存了一个七字节长的字符串<br>buf 属性是一个char类型的数组，数组的前七个字节分别保存了FANONEA中的这几个字符，最后一个字节则保留了空字符 ‘\0’ 。</p>
<p>SDS 遵循C字符串以空字符结尾的惯例，保存空字符的1字节空间不计 算再SDS的 len 属性里面，并且为空字符分配额外的1字节空间，以及添加空字符串末尾等操作，都是SDS函数自动完成的。</p>
<h4 id="4-3-1-SDS-与-C字符串区别"><a href="#4-3-1-SDS-与-C字符串区别" class="headerlink" title="4.3.1 SDS 与 C字符串区别"></a>4.3.1 SDS 与 C字符串区别</h4><p><strong>1 优化获取字符串长度时间</strong><br>因为C字符串并不记录自身的长度信息，所以为了获取一个C字符串的长度，程序必须遍历整个字符串，对遇到的每一个字符都进行计数，直到遇到代表字符串结尾的空字符为止。复杂度O(N)。</p>
<p>而SDS在len属性中就已经记录了SDS本身的长度，所以获取一个SDS长度的复杂度为O(1)。设置和更新SDS长度的工作是由SDS的api在执行时自动完成的，使用sds无需进行任何手动修改长度的工作。</p>
<p>所以通过使用SDS而不是C字符串，Redis将获取字符串的长度所需要的复杂度从O(N)降低了O(1)，这确保了长度的工作不会是redis的性能瓶颈，即时是一个非常长的字符串，我们反复执行 STRLEN 命令，那么这个复杂度始终是O(1)，不会对系统造成任何的影响。</p>
<p><strong>2 杜绝缓冲区的溢出</strong><br>C字符串不记录自身长度带来的另一个问题就是容易造成缓冲区溢出。<br>例如：<br>strcat 函数将 src 字符串中 的内容拼接到 dest 字符串 的末尾。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">char</span> *<span class="hljs-title function_">strcat</span><span class="hljs-params">(<span class="hljs-type">char</span> *dest,<span class="hljs-type">const</span> <span class="hljs-type">char</span> *src)</span><br></code></pre></td></tr></table></figure>

<p>因为C字符串不记录自身长度，所以strcat假定用户在执行这个函数时，已经为 dest 分配了足够多的内存，可以容纳 src 字符串中的所有内容，而一旦这个假定不成立时，就会产生缓冲区溢出。</p>
<p>与C字符串不同的是，SDS的空间分配策略完全杜绝了发生缓冲区溢出的可能性：<br>当SDS API需要对SDS进行修改时，API会先检查SDS的空间是否满足修改所需的要求，如果不满足的话，API会自动将SDS的空间扩展至执行修改所需要的大小，然后才执行实际的修改操作，所以使用SDS既不需要手动修改SDS的空间大小，也不会出现前面所说的缓冲区溢出的问题。</p>
<p><strong>3 减少修改字符串时时带来的内存重分配次数</strong></p>
<p>因为C字符串并不记录自身的长度，所以对于一个包含了N个字符的C字符串来说，这个字符串的底层实现总是一个N+1个字符长的数组（额外的一个字符串空间用来保存空字符串）。</p>
<p>所以字符串的长度和底层数组的长度之间存在着这种关联性，所以每次增长或是缩短一个C字符串 ，程序都总要对保存这个C的字符串数组进行一次内存重分配操作：</p>
<ul>
<li>如果要执行append拼接操作，那么就在执行这个操作之前，程序需要先通过内存重分配来扩展底层数组的空间大小，如果忘了这一步就会造成<code>缓冲区的溢出。</code></li>
<li>如果程序是 trim 缩短字符串的操作，那么在执行这个操作之后，程序需要通过内存重分配来释放字符串不再使用那部分空间，如果忘记了这一步就会产生内存泄漏。</li>
</ul>
<p>在一般程序中，如果修改字符串长度的情况不太常出现，那么每次修改都执行一次内存重分配是可以接受的。<br>但是redis作为数据库，常常被用于速度要求严苛，数据被频繁修改的场景，如果每次修改字符串的长度都需要执行一次内存重分配的话，那么光是执行内存重分配的时间就会占去 修改字符串 所需要的大部分时间，如果修改频繁发生就会对性能造成影响。</p>
<p>所以为了避免这种情况，SDS通过未使用空间free解除了字符串长度和底层数组长度之间的关联：</p>
<blockquote>
<p>在SDS中，buf数组的长度不一定是字符数量+1，数组里面可以包含未使用的字节，而这些字节的数量就由SDS的free属性记录。</p>
</blockquote>
<p><strong>3.1 空间预分配</strong></p>
<p>空间预分配用于优化SDS的字符串增长操作：当SDS的API对一个SDS进行修改，并且需要对SDS进行空间扩展的时候，程序不仅会为SDS分配修改所必须要的空间，还会为SDS分配额外未使用的空间。</p>
<p>其中，额外分配的未使用空间数量由以下公式决定：</p>
<ol>
<li>如果对SDS进行修改之后，SDS的长度（也就是len属性的值）将小于1MB，那么程序会分配和 len属性同样大小的未使用空间，这时候SDS的 len属性将和 free属性的值一致。</li>
<li>如果对SDS修改之后的长度大于 1MB。那么程序就会分配1MB的未使用空间。</li>
</ol>
<p>通过空间预分配策略，Redis可以 减少连续执行字符串增长操作所需的内存重分配次数。</p>
<p><strong>3.2 惰性空间释放</strong></p>
<p>惰性空间释放用于优化 SDS 的字符串缩短操作：当SDS的API需要缩短SDS保存的字符串时，程序并不立即使用内存重分配来回收缩短后多出来的字节，而是使用free属性将这些字节的数量记录起来，并等待将来使用。</p>
<p>于此同时，SDS也提供了对应的API，让我们可以在有需要的时候，真正地释放SDS的未使用空间，所以不用担心惰性空间释放策略会造成内存浪费。</p>
<p><strong>4 二进制安全</strong></p>
<p>C字符串中的字符必须符合某种编码（比如ASCⅡ），并且除了字符串的末尾之外，字符串里面不能包含空字符串，否则最先被程序读入的空字符串将被误认为是字符串结尾，这些限制使用C字符串只能保存文本数据，而不能保存像图片、音频、视频、压缩文件这种的二进制数据。</p>
<p>虽然数据库一般用于存储文本数据，但是数据库保存二进制数据的场景并不少见。因此，为了确保Redis可以适用于各种不同的使用场景，SDS的API都是二进制安全的，所以SDS API都会处理二进制的方式来处理buf数组里的数据，程序不会对其中的数据做任何限制，过滤或是假设。数据写入的时候什么样子，读取出来的时候就是什么样子。</p>
<p>这也是我们将SDS的buf属性成为字节数组的原因，Redis不是用这个数组来保存字符，而是用他来保存一系列的二进制数据。</p>
<p>通过使用二进制安全的SDS，而不是C字符串，使用Redis不仅可以保存文本数据，还可以保存任意格式的二进制数据。</p>
<figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><code class="hljs xquery">语法：set<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span> [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|keepttl] [nx|xx] [get]<br>说明：设<span class="hljs-built_in">置string</span>类型的键值，如<span class="hljs-built_in">果key</span>已经保存了一个值，那么这个操作会直接覆盖原来的值，并且忽略原始类型<br>   <span class="hljs-built_in"> key</span>：存储<span class="hljs-built_in">的key</span>名称<br>    <span class="hljs-keyword">value</span>：存储的<span class="hljs-keyword">value</span>数据<br>    [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|keepttl]：<br>        ex seconds：设置<span class="hljs-built_in">键key</span>的过期时间，单位时秒<br>        px milliseconds：设置<span class="hljs-built_in">键key</span>的过期时间，单位时毫秒<br>        exat timestamp：设置键过期的指定Unix时间，以秒为单位<br>        pxat milliseconds-timestamp：设置键过期的指定Unix时间，以毫秒为单位<br>        keepttl：获<span class="hljs-built_in">取key</span>的过期时间<br>    [nx|xx]：<br>        nx：只有<span class="hljs-built_in">键key</span>不存在的时候才会设<span class="hljs-built_in">置key</span>的值<br>        xx：只有<span class="hljs-built_in">键key</span>存在的时候才会设<span class="hljs-built_in">置key</span>的值<br>    [get]：返回<span class="hljs-built_in"> key</span> 存储的值，如果<span class="hljs-built_in"> key</span> 不存在返回空<br>set<span class="hljs-built_in"> name</span> zhangsan   -- 设置一个最基本的键值<br>set<span class="hljs-built_in"> name</span> zhangsan ex <span class="hljs-number">60</span>     -- 设置一个键值，并指定过期时间秒，ttl可以查看过期时间<br>set<span class="hljs-built_in"> name</span> zhangsan nx        -- 设置一个键值，但是加上nx 代表只能更新已经存在的，如不存<span class="hljs-built_in">在name</span>键则无法添加<br>set<span class="hljs-built_in"> name</span> zhangsan xx        -- 设置一个键值，但是xx和nx相反<br><br>语法：setnx<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span><br>说明：设置键值，存在此键则返回<span class="hljs-number">0</span>不覆盖，否则正常设置<br>    setnx<span class="hljs-built_in"> name</span> zhangsan     -- 设<span class="hljs-built_in">置name</span>为键，并赋值<br><br>语法：setrange<span class="hljs-built_in"> key</span> offset <span class="hljs-keyword">value</span><br>说明：偏移量offset&gt;=<span class="hljs-number">0</span>开始， 用<span class="hljs-keyword">value</span>参数覆盖<span class="hljs-built_in">键key</span>储存的字符串值。不存在的<span class="hljs-built_in">键key</span>当作空白字符串处理。<br>    set<span class="hljs-built_in"> name</span> zhangsan   --创建原始键值<br>    setrange<span class="hljs-built_in"> name</span> <span class="hljs-number">5</span> <span class="hljs-string">&#x27; yu xiao&#x27;</span><br>        -- 把原有的 zhangsan 从第五位之后更改（<span class="hljs-number">0</span>下标）；最终变为 <span class="hljs-string">&quot;zhang yu xiao&quot;</span><br>    setrange<span class="hljs-built_in"> name</span> <span class="hljs-number">14</span> out<br>        -- 超出偏移则使用空格 <span class="hljs-string">&#x27;\x00&#x27;</span> 代替一个空格；最终变为 <span class="hljs-string">&quot;zhang yu xiao\x00out&quot;</span><br>    setrange address <span class="hljs-number">2</span> anhui<br>        -- 如果设置的键不存在则会新建，但是偏移量会以空格代替；最终变为 <span class="hljs-string">&quot;\x00\x00anhui&quot;</span><br><br>语法：strlen<span class="hljs-built_in"> key</span><br>说明：获取指<span class="hljs-built_in">定key</span>所储存的字符串值的长度。<span class="hljs-built_in">当key</span>储存的不是字符串类型时，返回错误。<br><br>语法：append<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span><br>说明：用于为指定<span class="hljs-built_in">的key</span>追加值，成功后返回当前键里面的字符串全部长度（如果追加有空格需要使用 <span class="hljs-string">&#x27;&#x27;</span>）<br>    append<span class="hljs-built_in"> name</span> <span class="hljs-string">&#x27;good good boy&#x27;</span>     -- 追加有空格的，并且成功后返回当<span class="hljs-built_in">前key</span>的全部长度<br><br>语法：get<span class="hljs-built_in"> key</span><br>说明：如果<span class="hljs-built_in">键key</span>不存在，那么返回特殊值nil；否则返回<span class="hljs-built_in">键key</span>的值。<br>    get<span class="hljs-built_in"> name</span> -- 获<span class="hljs-built_in">取name</span>键的值<br><br>语法：getdel<span class="hljs-built_in"> key</span><br>说明：先获取到指定<span class="hljs-built_in">的key</span>后，再删除获取的那<span class="hljs-built_in">个key</span>；最终返回被删除的值<br><br>语法：getset<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span><br>说明：设置更<span class="hljs-built_in">新key</span>值，设置前先把原有的值返回出来，并设置新的值，如<span class="hljs-built_in">果key</span>不存在时使用getset则返回nil，并设置新值<br><br>语法：getrange<span class="hljs-built_in"> key</span> <span class="hljs-keyword">start</span> <span class="hljs-keyword">end</span><br>说明：获取指定的范围值，<span class="hljs-keyword">start</span>（从<span class="hljs-number">0</span>开始）<span class="hljs-keyword">end</span>（从<span class="hljs-number">0</span>开始）<br>    set<span class="hljs-built_in"> name</span> zhangsan<br>    getrange<span class="hljs-built_in"> name</span> <span class="hljs-number">2</span> <span class="hljs-number">5</span>  -- 获取范围值，最终返回 <span class="hljs-string">&#x27;angs&#x27;</span><br>    getrange<span class="hljs-built_in"> name</span> <span class="hljs-number">3</span> -<span class="hljs-number">2</span> -- 获取范围值，最终返回 <span class="hljs-string">&#x27;ngsa&#x27;</span><br>    注：若使用getrange<span class="hljs-built_in"> name</span> <span class="hljs-number">0</span> -<span class="hljs-number">1</span> (其中-<span class="hljs-number">1</span>代表从后往前数)<br><br>语法：getex<span class="hljs-built_in"> key</span> [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|persist]<br>说明：获取指定<span class="hljs-built_in">的key</span>值，并且获取后可以对当<span class="hljs-built_in">前key</span>设置超时时间或者清除超时时间<br>    [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|persist]：<br>         ex seconds：设置<span class="hljs-built_in">键key</span>的过期时间，单位时秒<br>         px milliseconds：设置<span class="hljs-built_in">键key</span>的过期时间，单位时毫秒<br>         exat timestamp：设置键过期的指定Unix时间，以秒为单位<br>         pxat milliseconds-timestamp：设置键过期的指定Unix时间，以毫秒为单位<br>         persist：清除超时时间<br><br>语法：mset<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span> <span class="hljs-built_in">[key</span> <span class="hljs-keyword">value</span> ...]<br>说明：和set命令差不多，但是这个是批量设置，如果设置键存在则覆盖，不存在则添加<br>    mset<span class="hljs-built_in"> name</span> zhangsn age <span class="hljs-number">22</span> address anhui  -- 批量设<span class="hljs-built_in">置name</span>和age和address<br><br>语法：mget<span class="hljs-built_in"> key</span> <span class="hljs-built_in">[key</span> ...]<br>说明：批量获取键的值，如果获取的某个不存在则返回（nil），其它正常返回<br>    mget<span class="hljs-built_in"> name</span> aaa   -- 批量获<span class="hljs-built_in">取name</span>和aaa的值（aaa键不存在则返回nil）<br><br>语法：setex<span class="hljs-built_in"> key</span> seconds <span class="hljs-keyword">value</span><br>说明：将<span class="hljs-built_in">键key</span>的值设置为<span class="hljs-keyword">value</span> ，并将<span class="hljs-built_in">键key</span>的过期时间设置为seconds秒钟，如<span class="hljs-built_in">果key</span>存在则覆盖原有值<br>    setex<span class="hljs-built_in"> name</span> <span class="hljs-number">60</span> zhangsan  -- 设<span class="hljs-built_in">置key</span><span class="hljs-built_in">为name</span>，并且设置<span class="hljs-number">60</span>秒过期时间<br><br>语法：psetex<span class="hljs-built_in"> key</span> milliseconds <span class="hljs-keyword">value</span><br>说明：将<span class="hljs-built_in">键key</span>的值设置为<span class="hljs-keyword">value</span> ，并将<span class="hljs-built_in">键key</span>的过期时间设置为milliseconds毫秒，如<span class="hljs-built_in">果key</span>存在则覆盖原有值<br>    psetex<span class="hljs-built_in"> name</span> <span class="hljs-number">70000</span> zhangsan  -- 设<span class="hljs-built_in">置key</span><span class="hljs-built_in">为name</span>，并且设置<span class="hljs-number">70</span>秒过期时间<br><br>语法：msetnx<span class="hljs-built_in"> key</span> <span class="hljs-keyword">value</span> <span class="hljs-built_in">[key</span> <span class="hljs-keyword">value</span> ...]<br>说明：当且仅当所有给定键都不存在时，为所有给定键设置值（如果添加的其中键在当前数据库存在则都不成功）<br>    msetnx是一个原子性(atomic)操作，所有给定键要么就全部都被设置，要么就全部都不设置<br>    msetnx<span class="hljs-built_in"> name</span> zhangsan age <span class="hljs-number">22</span> -- 设<span class="hljs-built_in">置name</span>和age两个键值<br><br>语法：incr<span class="hljs-built_in"> key</span><br>说明：<span class="hljs-built_in">将key</span>中储存的数字值增一，并返回增加后的值（只能用在整型，字符串啥的会报错）<br><br>语法：incrby<span class="hljs-built_in"> key</span> increment<br>说明：<span class="hljs-built_in">将key</span>中储存的数字值增加指定步长increment，并返回增加后的值（只能用在整型，字符串啥的会报错）<br><br>语法：incrbyfloat<span class="hljs-built_in"> key</span> increment<br>说明：<span class="hljs-built_in">将key</span>中储存的数字值增加指定步长increment，并返回增加后的值（只能用在浮点型，字符串啥的会报错）<br>    incrbyfloat salary <span class="hljs-number">333.33</span>   -- 对salary添加步长<span class="hljs-number">333.33</span><br><br>语法：decr<span class="hljs-built_in"> key</span><br>语法：<span class="hljs-built_in">将key</span>中储存的数字值减一，并返回减后的值（只能用在整型，字符串啥的会报错）<br><br>语法：decrby<span class="hljs-built_in"> key</span> decrement<br>说明：<span class="hljs-built_in">将key</span>中储存的数字值减指定步长increment，并返回减后的值（只能用在整型，字符串啥的会报错）<br></code></pre></td></tr></table></figure>



<h3 id="4-4-Hash（哈希表）类型命令"><a href="#4-4-Hash（哈希表）类型命令" class="headerlink" title="4.4 Hash（哈希表）类型命令"></a>4.4 Hash（哈希表）类型命令</h3><ul>
<li><p>hash是一个string 类型的field和value的映射表。</p>
</li>
<li><p>Redis 中每个 hash 可以存储40多亿键值对。</p>
</li>
<li><p>采用key—field—value的方式。一个key可对应多个field，一个field对应一个value，与Java中的HashMap相似，是一组键值对的集合，且支持单独对其中一个键进行增删改查操作。</p>
</li>
<li><p>hash特别适合存储对象，相对于将对象的每个字段存成单个string 类型。一个对象存储在hash类型中会占用更少的内存，并且可以更方便的存取整个对象。</p>
</li>
</ul>
<p><strong>应用场景：</strong></p>
<ul>
<li>适用于存储对象（object）信息，如用户详细信息，用户id为key，他的姓名、性别、年龄等信息为field-value。</li>
<li>存储多个用户信息，可以将filed存为用户id，每个用户的信息序列化为json存于value。</li>
</ul>
<p>String与Hash的对比</p>
<p>1.采用String类型的存储对象，需要将对象序列化</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230221161015421.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230221161015421"></p>
<p>增加了序列化/反序列化的开销，并且在需要修改其中一项信息时，需要把整个对象取回。</p>
<p>2.使用Hash数据类型则不需要将对象整个取出</p>
<p>Key仍然是用户ID, value是一个Map，这个Map的key是成员的属性名，value是属性值，这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了，既不需要重复存储数据，也不会带来序列化和反序列化。<br> <img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230221161126363.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230221161126363"></p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：hset key field value [field value ...]<br>说明：用于为存储在key中的哈希表的field字段赋值value<br>    hset student name zhangsan age 22   -- 设置key为student但里面存储着name和age字段<br><br>语法：hmset key field value [field value ...]<br>说明：用于同时将多个field-value(字段-值)对设置到哈希表中。此命令会覆盖哈希表中已存在的字段<br>    注：Redis4.0.0起被废弃，推荐使用hset，它也可以一次性添加多个<br><br>语法：hsetnx key field value<br>说明：用于为存储在key中的哈希表的field字段赋值value；如果当前field存在则添加失败（不可覆盖添加）<br><br>语法：hget key field<br>说明：用于返回哈希表中指定字段field的值<br>    hget student name  -- 获取哈希表里的field字段<br><br>语法：hmget key field [field ...]<br>说明：用于返回哈希表中指定字段field的值；但是可以一次性返回多个field值<br>     hmget student name age     --  获取哈希表里的field多个字段<br><br>语法：hdel key field [field ...]<br>说明：用于删除哈希表key中的一个或多个指定字段，不存在的字段将被忽略。如果key不存在，会被当作空哈希表处理并返回0<br>     hdel student name  -- 删除哈希表中key为student里的name字段<br><br>语法：hexists key field<br>说明：用于查看哈希表的指定字段field是否存在，1存在，0不存在<br>    hexists student name    -- 查看哈希表中key为student里的name字段是否存在<br><br>语法：hgetall key<br>说明：用于返回存储在key中的哈希表中所有的field和value。<br><br>语法：hkeys key<br>说明：返回存储在key中哈希表的所有field<br><br>语法：hvals key<br>说明：返回存储在key中哈希表的所有value<br><br>语法：hincrby key field increment<br>说明：为哈希表key中的field的值加上指定的增量，并返回增量后的值（增量正数累加，增量负数递减）<br>    hincrby student age 2       -- 对年龄累加<br>    hincrby student age -28     -- 对年龄递减<br>    注：当前命令只可操作整数类型，而字符串，浮点类型啥的会报错<br><br>语法：hincrbyfloat key field increment<br>说明：为哈希表key中的field的值加上指定的增量，并返回增量后的值（增量正数累加，增量负数递减）<br>    hincrby student salary 300.3       -- 对工资累加<br>    hincrby student salary -432.84     -- 对工资递减<br>    注：当前命令只可操作整数类型、浮点类型，而操作字符串会报错<br><br>语法：hstrlen key field<br>说明：返回存储在key中给定field相关联的值的字符串长度（string length）<br><br>语法：hlen key<br>说明：用于获取哈希表中字段(fields)的数量<br><br>语法：hrandfield key [count [withvalues]]<br>说明：随机返回key里的field字段<br>    count：返回的field个数，如果是正数且超过key里的field总数则全部返回，<br>                          如果是负数且超过key里的field总数绝对值则返回空，<br>        整数则返回不重复字段，负数则可能返回重复字段<br>        注：在传 1 或 -1 它是随机在key里面选择，<br>        在传 2 则随机返回key里的2个field字段，这两个field不可能存在重复<br>        在传-2 则随机返回key里的2个field字段，这两个field可能会存在重复<br>    withvalues：返回field字段时后面还返回当前field的值<br><br>    hset student name zhangsan age 22 address anhui salary 3000.30  -- 初始化<br>    127.0.0.1:6379&gt; hrandfield student -2<br>    1) &quot;salary&quot;<br>    2) &quot;salary&quot;<br>    127.0.0.1:6379&gt; hrandfield student 2<br>    1) &quot;name&quot;<br>    2) &quot;age&quot;<br>    127.0.0.1:6379&gt; hrandfield student 2 withvalues<br>    1) &quot;salary&quot;<br>    2) &quot;3000.30&quot;<br>    3) &quot;name&quot;<br>    4) &quot;zhangsan&quot;<br><br>语法：hscan key cursor [match pattern] [count count]<br>说明：用于遍历哈希表中的键值对<br>    cursor：游标（告诉迭代器从哪开始迭代）<br>    [match pattern]：过滤筛选条件<br>    [count count]：迭代的个数<br>    hscan student 0 match * count 2<br>        -- 迭代student里的field字段，下标0开始，过滤条件*全部，但是每次迭代count为2，但2不起作用<br><br></code></pre></td></tr></table></figure>



<h3 id="4-5-List（集合）类型命令"><a href="#4-5-List（集合）类型命令" class="headerlink" title="4.5 List（集合）类型命令"></a>4.5 List（集合）类型命令</h3><p>List类型有两种实现方式: ziplist和linkedlist进行实现的</p>
<p>压缩列表（ziplist）是一组连续内存块组成的顺序的数据结构，压缩列表能够节省空间，压缩列表中使用多个节点来存储数据。</p>
<p>压缩列表是列表键和哈希键底层实现的原理之一，压缩列表并不是以某种压缩算法进行压缩存储数据，而是它表示一组连续的内存空间的使用，节省空间，压缩列表的内存结构图如下：</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-6a84f8a661df7996fbcd4ca65c1a40b6_720w.webp" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>压缩列表中每一个节点表示的含义如下所示：</p>
<ol>
<li><code>zlbytes</code>：4个字节的大小，记录压缩列表占用内存的字节数。</li>
<li><code>zltail</code>：4个字节大小，记录表尾节点距离起始地址的偏移量，用于快速定位到尾节点的地址。</li>
<li><code>zllen</code>：2个字节的大小，记录压缩列表中的节点数。</li>
<li><code>entry</code>：表示列表中的每一个节点。</li>
<li><code>zlend</code>：表示压缩列表的特殊结束符号<code>&#39;0xFF&#39;</code>。</li>
</ol>
<p>在压缩列表中每一个entry节点又有三部分组成，包括<code>previous_entry_ength、encoding、content</code>。</p>
<ol>
<li><code>previous_entry_ength</code>表示前一个节点entry的长度，可用于计算前一个节点的其实地址，因为他们的地址是连续的。</li>
<li>encoding：这里保存的是content的内容类型和长度。</li>
<li>content：content保存的是每一个节点的内容。</li>
</ol>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-8fd5104994d92443af483d747d631390_720w.webp" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>linkedlist是一个双向链表，他和普通的链表一样都是由指向前后节点的指针。插入、修改、更新的时间复杂度尾O(1)，但是查询的时间复杂度确实O(n)。</p>
<p>linkedlist底层实现是采用链表进行实现，在c语言中并没有内置的链表这种数据结构，Redis实现了自己的链表结构。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span>  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span>&#123;</span><br>       <span class="hljs-comment">//前置节点</span><br>       <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span> *<span class="hljs-title">prev</span>;</span><br>       <span class="hljs-comment">//后置节点</span><br>       <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">listNode</span> *<span class="hljs-title">next</span>;</span><br>       <span class="hljs-comment">//节点的值</span><br>       <span class="hljs-type">void</span> *value;  <br>&#125;listNode<br></code></pre></td></tr></table></figure>

<p>多个 listNode 可以通过 prev 和 next 指针组成双端链表，结构如下</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/99e1254f32444e6a87a891ff643baa26.jpg" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="Redis五种数据类型"></p>
<p>另外Redis还提供了操作链表的数据结构：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list</span>&#123;</span><br>     <span class="hljs-comment">//表头节点</span><br>     listNode *head;<br>     <span class="hljs-comment">//表尾节点</span><br>     listNode *tail;<br>     <span class="hljs-comment">//链表所包含的节点数量</span><br>     <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> len;<br>     <span class="hljs-comment">//节点值复制函数</span><br>     <span class="hljs-type">void</span> (*<span class="hljs-built_in">free</span>) (<span class="hljs-type">void</span> *ptr);<br>     <span class="hljs-comment">//节点值释放函数</span><br>     <span class="hljs-type">void</span> (*<span class="hljs-built_in">free</span>) (<span class="hljs-type">void</span> *ptr);<br>     <span class="hljs-comment">//节点值对比函数</span><br>     <span class="hljs-type">int</span> (*match) (<span class="hljs-type">void</span> *ptr,<span class="hljs-type">void</span> *key);<br>&#125;<span class="hljs-built_in">list</span>;<br></code></pre></td></tr></table></figure>

<p>list结构为链表提供了表头指针 head ，表尾指针 tail 以及链表长度计数器 len ，dup、free、match 成员则是用于实现多态链表所需的类型特定函数。</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-102238a40860c08faf37c492f61f6d94_720w.webp" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p><strong>Redis链表实现的特性</strong>：</p>
<ul>
<li>双端：链表节点带有 prev 和 next 指针，获取某个节点的前置节点和后置节点复杂度都是O(1)。</li>
<li>无环：表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL，对链表的访问以NULL为终点。</li>
<li>带表头指针和表尾指针：通过list结构的 head 和 tail 指针，程序获取链表的表头节点和表尾结点的复杂度都是O(1)。</li>
<li>带链表长度计数器：程序使用 list 结构的 len属性对 list持有的链表节点进行计数，程序获取链表中节点数量的复杂度为O(1)。</li>
<li>多态：链表节点使用 void* 指针来保存节点值，并且通过 list 结构的 dup、 free、match 三个属性为节点值设置类型特定函数，所以链表可以用于保存各种不同类型的值。</li>
</ul>
<p>同时满足两个条件：列表对象保存的所有字符串元素的长度都小于64字节；列表元素保存的元素数量小于512个；列表对象采用 ziplist 编码，否则采用 linkedlist 编码</p>
<p><strong>应用场景：</strong></p>
<ul>
<li><p>存储key-list数据，具有一对多的数据关系，如可以制作微博的用户关注列表；</p>
</li>
<li><p>可以当作消息队列，可以利用 List 的 PUSH 操作，将任务存在 List 中，然后工作线程再用 POP 操作将任务取出进行执行。</p>
</li>
</ul>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：lpush key element [element ...]<br>说明：将一个或多个值插入到集合key的头部（头插法），如果当前key不存在则创建新的<br>     lpush listNumber 8.4 13 14 10.5 4 19.6 10 14 5.2 10 3 2.5 7 4.7 10 11.2 8 2.2 15.7 20.9<br>     lpush listString  remini Momen Pledg Memo Tende Biode Revie silen Romanti AusL Simpl 　　　　　　Promis Romanti Bautifu smil Initiall sunse lemo firs Chaffere<br>        -- 插入两个案例，后面以这个说明<br><br>语法：lpushx key element [element ...]<br>说明：往集合左边插入一个元素；若集合key不存在无法插入<br><br>语法：rpush key element [element ...]<br>说明：将一个或多个值插入到集合key的尾部（尾插法），如果当前key不存在则创建新的<br><br>语法：rpushx key element [element ...]<br>说明：往集合右边插入一个元素；若集合key不存在无法插入<br><br>语法：lpop key [count]<br>说明：从集合左边（头部）弹出（删除）指定的count个元素删除<br>    lpop listString 2   -- 从集合左边弹出两个元素删除<br><br>语法：rpop key [count]<br>说明：从集合右边（尾部部）弹出（删除）指定的count个元素删除<br><br>语法：blpop key [key ...] timeout<br>说明：移出并获取集合头部第一个元素，如果集合没有元素会阻塞集合直到等待超时或发现可弹出元素为止，它是lpop的阻塞版<br>    key：如果当前key不存在或者key内部没元素，则一直阻塞等待，等待其它客户端创建此key和元素，会立马弹出<br>        但是超出延迟时间的话还没有弹出元素则会在最后弹出(nil)<br>    [key ...]：设置多个key时，如果第一个key不存在则会考虑弹出第二个key,第三个key....，如果每个key都不存在或没元素则当前客户端会进入一个阻塞状态，直到有元素弹出，或者自动超时弹出(nil)<br>    <br>    127.0.0.1:6379&gt; blpop listA mylist 480<br>    1) &quot;mylist&quot;<br>    2) &quot;remini&quot;<br>        -- 设置两个key，其中listA为空，会自动去找mylist集合，发现存在元素，并立刻弹出<br><br>语法：brpop key [key ...] timeout<br>说明：移出并获取集合尾部第一个元素，如果集合没有元素会阻塞集合直到等待超时或发现可弹出元素为止，它是lpop的阻塞版<br><br>语法：llen key<br>说明：获取到集合里元素的总个数<br><br>语法：lrange key start stop<br>说明：查询集合元素，并设置查询区间<br>    start：起始值，设置正数则从左往右，设置负数则从右往左开始<br>    stop：终点值，设置正数则从左往右，设置负数则从右往左开始<br>    注：start（0） stop（-1）代表查询全部<br>    lrange listString -5 -3<br>        -- 起点从尾部往前数5个，终点从尾部往前数3个；最终显示 -5，-4，-3这三个元素<br>    lrange listString -5 -8<br>        -- 起点从尾部往前数5个，终点从尾部往前数8个；最终显示(empty array)<br><br>语法：lindex key index<br>说明：返回集合key里索引index位置存储的元素，0~n从左往右索引、-1~-n从右往左索引<br>    lindex listString -1    --  获取集合listString里的最后一个索引的元素<br><br>语法：lrem key count element<br>说明：从集合key中删除前count个值等于element的元素<br>    count &gt; 0: 从头到尾删除值为 value 的元素<br>    count &lt; 0: 从尾到头删除值为 value 的元素<br>    count = 0: 移除所有值为 value 的元素<br>    lrem listString -2 Romanti<br>        -- 移除集合listString中的Romanti元素，删除个数-2（代表从尾部查找并删除两个），并返回删除成功个数<br><br>语法：lset key index element<br>说明：设置集合key中index位置的元素值为新的element，index为正数则从头到位索引，为负数从尾到头索引查询<br>     lset listString 2 yyds  -- 修改集合listString中索引为2的元素为yyds<br><br>语法：linsert key before|after pivot element<br>说明：把element元素插入到指定集合key里，但是还要以pivot内部的一个元素为基准，看是插到这个元素的左边还是右边<br>    before|after：插入元素的前后位置选项<br>    pivot：集合里的参考元素<br>    element：待插入的元素<br>    注：当集合key不存在时，这个list会被看作是空list，什么都不执行<br>    注：当集合key存在，值不是列表类型时，返回错误<br>    注：当给定的参考元素pivot不存在是则返回-1，因为程序不知道往哪插入<br>    linsert listString after Romanti niubi -- 把niubi插入到listString集合里，插入参考Romanti元素的后面<br><br>语法：lpos key element <br>说明：返回集合key中匹配给定element成员的索引<br>    key：要查询的集合key<br>    element：要查询索引的元素<br>    1) (integer) 7<br><br>语法：lmove source destination left|right left|right<br>说明：用于原子地从source集合左边或者右边弹出一个元素，添加到destination新集合里的左边或右边<br>    source：源集合<br>    destination：目标集合<br>    left|right left|right：<br>        第一个：代表从源集合的左边或者右边弹出元素<br>        第二个：代表从目标集合的左边或者右边添加<br>    lmove listString mylist left right<br>        -- 从listString源集合的左边弹出个元素，添加到mylist目标集合的右边<br><br>语法：ltrim key start stop<br>说明：修订一个已经存在的集合；修订一个指定范围的元素放到当前集合中<br>    127.0.0.1:6379&gt; ltrim listString -10 -5<br>    OK<br>    127.0.0.1:6379&gt; lrange listString 0 -1<br>    1) &quot;Simpl&quot;<br>    2) &quot;AusL&quot;<br>    3) &quot;silen&quot;<br>    4) &quot;Revie&quot;<br>    5) &quot;Biode&quot;<br>    6) &quot;Tende&quot;<br><br></code></pre></td></tr></table></figure>



<h3 id="4-6-Set（无序集合）类型命令"><a href="#4-6-Set（无序集合）类型命令" class="headerlink" title="4.6 Set（无序集合）类型命令"></a>4.6 Set（无序集合）类型命令</h3><ul>
<li><p>集合成员是唯一的，这就意味着集合中不能出现重复的数据。</p>
</li>
<li><p>集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是 O(1)。</p>
</li>
<li><p>可以进行取并集，交集，差集操作。</p>
</li>
<li><p>每个集合可存储40多亿个成员。</p>
</li>
</ul>
<p>应用场景：</p>
<ul>
<li><p>查找共同好友、二度好友，共同喜好。</p>
</li>
<li><p>利用唯一性，可以统计访问网站的所有独立 IP。</p>
</li>
<li><p> 好友推荐的时候，根据 tag 求交集，大于某个 threshold 就可以推荐</p>
</li>
</ul>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：sadd key member [member ...]<br>说明：将一个或多个元素加入到集合中，添加已存在的集合元素将被忽略（不会添加上），返回添加成功的元素个数<br>    127.0.0.1:6379&gt; sadd mysetA zhangsan lisi wangwu mazi zhangsan<br>    (integer) 4<br>        -- 成功添加四个元素，其中一个为重复的，无法添加<br><br>语法：srem key member [member ...]<br>说明：删除指定的元素；如果指定的元素不是集合成员则被忽略，返回被删除元素个数，不含不存在的元素<br><br>语法：spop key [count]<br>说明：从集合key中删除一个或多个随机元素，并返回删除的元素<br><br>语法：srandmember key [count]<br>说明：随机返回集合key中的一个或多个随机元素，若返回个数的count大于集合总数则返回全部<br>    注：srandmember随机返回不删除原集合，spop返回并删除原集合返回的元素<br><br>语法：sdiff key [key ...]<br>说明：返回第一个集合与其它集合之间的差异；就是第一个集合的某个元素在其它集合都不存在则这个元素会被返回，<br>    key1 = &#123;a,b,c,d&#125;<br>    key2 = &#123;c&#125;<br>    key3 = &#123;a,c,e&#125;<br>    SDIFF key1 key2 key3  = &#123;b,d&#125;<br>    注：如果只携带一个key比较则会返回当前集合全部元素<br>    举例：<br>        127.0.0.1:6379&gt; sadd mysetA zhangsan lisi wangwu mazi zhangsan<br>        (integer) 4<br>        127.0.0.1:6379&gt; sadd mysetB anhui shanghai zhangsan mazi<br>        (integer) 4<br>        127.0.0.1:6379&gt; sdiff mysetA mysetB<br>        1) &quot;wangwu&quot;<br>        2) &quot;lisi&quot;<br><br>语法：sdiffstore destination key [key ...]<br>说明：此命令和sdiff功能差不多，不同的是它将结果保存到destination集合，并返回成功添加到新集合上的个数。<br>    127.0.0.1:6379&gt; sdiffstore newmyset mysetA mysetB<br>    (integer) 2<br><br>语法：sinter key [key ...]<br>说明：返回第一个集合与其它集合之间的交集；说白就是第一个集合的某个元素在其它集合都存在则这个元素会被返回，<br>    key1 = &#123;a,b,c,d&#125;<br>    key2 = &#123;c&#125;<br>    key3 = &#123;a,c,e&#125;<br>    SINTER key1 key2 key3 = &#123;c&#125;<br>    注：如果只携带一个key比较则会返回当前集合全部元素<br>    举例：<br>        127.0.0.1:6379&gt; sadd mysetA zhangsan lisi wangwu mazi zhangsan<br>        (integer) 4<br>        127.0.0.1:6379&gt; sadd mysetB anhui shanghai zhangsan mazi<br>        (integer) 4<br>        127.0.0.1:6379&gt; sinter mysetA mysetB<br>        1) &quot;zhangsan&quot;<br>        2) &quot;mazi&quot;<br><br>语法：sinterstore destination key [key ...]<br>说明：此命令和sinter功能差不多，不同的是它将结果保存到destination集合，并返回成功添加到新集合上的个数。<br>    127.0.0.1:6379&gt; sinterstore newmyset mysetA mysetB<br>    (integer) 2<br><br>语法：sunion key [key ...]<br>说明：用于返回所有给定集合的并集<br>    key1 = &#123;a,b,c,d&#125;<br>    key2 = &#123;c&#125;<br>    key3 = &#123;a,c,e&#125;<br>    sunion key1 key2 key3 = &#123;a,b,c,d,e&#125;<br>    注：如果只携带一个key比较则会返回当前集合全部元素<br>    举例：<br>        127.0.0.1:6379&gt; sadd mysetA zhangsan lisi wangwu mazi zhangsan<br>        (integer) 4<br>        127.0.0.1:6379&gt; sadd mysetB anhui shanghai zhangsan mazi<br>        (integer) 4<br>        127.0.0.1:6379&gt; sunion mysetA mysetB<br>        1) &quot;mazi&quot;<br>        2) &quot;zhangsan&quot;<br>        3) &quot;lisi&quot;<br>        4) &quot;anhui&quot;<br>        5) &quot;wangwu&quot;<br>        6) &quot;shanghai&quot;<br><br>语法：sunionstore destination key [key ...]<br>说明：类似于sunion，不同的是不返回结果集，而是把返回存储在destination集合中。<br>    127.0.0.1:6379&gt; sunionstore newset mysetA mysetB<br>    (integer) 6<br><br>语法：scard key<br>说明：返回集合中元素的数量（整型值）<br><br>语法：smembers key<br>说明：返回存储在key中的集合的所有的成员<br><br>语法：sismember key member<br>说明：判断元素member是否是集合key的成员，是返回1，否则0<br>    127.0.0.1:6379&gt; sismember mysetA zhangsan<br>    (integer) 1<br><br>语法：smismember key member [member ...]<br>说明：批量判断元素members是否是集合key的成员，是返回1，否则0<br>    127.0.0.1:6379&gt; smismember mysetA zhangsan lisi<br>    1) (integer) 1<br>    2) (integer) 1<br><br>语法：smove source destination member<br>说明：从集合source中移动成员member到集合destination；<br>    注：返回1代表成功，返回0代表移动element元素在source不存在<br>    注：移动成功后会把element元素在原集合上删除<br>    注：若被移动的元素在两个集合都存在，则会覆盖移动，再删除原集合上的元素<br>    smove mysetA mysetB  zhangsan   -- 把元素zhangsan从集合mysetA移动到mysetB上<br><br><br></code></pre></td></tr></table></figure>



<h3 id="4-7-SortedSet（有序集合）类型命令"><a href="#4-7-SortedSet（有序集合）类型命令" class="headerlink" title="4.7 SortedSet（有序集合）类型命令"></a>4.7 SortedSet（有序集合）类型命令</h3><ul>
<li><p>Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。</p>
</li>
<li><p>每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。</p>
</li>
<li><p>有序集合的成员是唯一的,但分数(score)却可以重复。</p>
</li>
<li><p>集合是通过哈希表实现的，所以添加，删除，查找的复杂度都是 O(1)。 </p>
</li>
<li><p>每个集合可存储40多亿个成员。</p>
</li>
</ul>
<p>SortedSet使用一个额外的参数score为成员排序，内部使用hashmap和跳跃表实现存储和有序,HaspMap存放成员到score的映射，而跳跃表存放所有的成员,使用跳表实现比较高的查询效率。</p>
<p>ziplist和skiplist，根据不同情况使用不同的实现方式</p>
<p>1.元素个数小于128，同时所有member都小于64字节，使用ziplist</p>
<p>2.否则使用skiplist</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/bce9ce6e7dccd0edc378720ca82c465d.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>可以看出ziplist是当数据小的时候，由一段连续的内存组成,最大的优点就是节省内存，但这种结构不善于修改</p>
<p><strong>skiplist跳表</strong></p>
<p>skiplist跳表是可以实现二分查找的有序链表，是一种以空间换取时间的结构。<br>由于链表，无法进行二分查找，因此借鉴数据库索引的思想，提取出链表中关键节点（索引），先在关键节点上查找，再进入下层链表查找。<br><strong>跳表 = 链表 + 多级索引</strong></p>
<p>跳跃表是是指除了第一层以外,会建立多个稀疏的链表,这些链表会跳过部分节点，这使得我们查找数据的时候，从最高层查找数据，一层层向下查找，之后最后一层精确找到对应的数据,让我们的查找速度提高很多，如下图，查找一个23的数据</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230301171709024.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230301171709024"></p>
<p>先从第4层找到,和7进行比较,大于7,第4层7之后没有了数据<br>然后向下一层,从第3层的7节点向后找,比较37,小于37，继续向下找<br>从第2层找,比较再从7节点往后找,比较19,大于19,继续向后找,小于37,继续向下一层找<br>到第2层,从19节点开始找,比较22,大于22,向后找,比较26,小于26,则发现没有目标节点</p>
<p>sorted set是由zipList和dict,skipList基础上构建起来的,</p>
<p>当数据较少时，sorted set是由一个ziplist来实现的。<br>当数据多的时候，sorted set是由一个叫zset的数据结构来实现的，这个zset包含一个dict + 一个skiplist。dict用来查询数据到分数(score)的对应关系，而skiplist用来根据分数查询数据（可能是范围查找)</p>
<p><strong>应用场景：</strong></p>
<ul>
<li><p>带有权重的元素，比如用户的积分排行榜需求可以通过有序集合实现。</p>
</li>
<li><p>存储全班同学成绩的 Sorted Sets，其集合 value 可以是同学的学号，而 score 就可以是其考试得分</p>
</li>
</ul>
<p>1.添加命令</p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：zadd key [nx|xx] [gt|lt] [ch] [incr] score member [score member ...]<br>说明：用于将一个或多个member元素及其score值加入到有序集key当中，若添加的member已存在则更新当前分数<br>    [nx|xx]:<br>        nx：只能做添加操作<br>        xx：只能做更新操作<br>    [gt|lt]:<br>        gt：更新的元素分数必须比原分数大<br>        lt：更新的元素分数必须比原分数小<br>    [ch]：返回添加和更新成功的个数<br>    [incr]：累加操作，score代表更新member的步长<br>    注：gt,lt 和 nx 三者互斥不能同时使用<br>    注：分数的取值范围-9007199254740992到9007199254740992<br></code></pre></td></tr></table></figure>

<p>使用</p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs txt"><br>zadd china -10 Anhui 45 Shanghai 24 Beijing 33 Henan 87 Guangdong<br>    -- 基本使用<br>zadd china 47 Shanghai<br>    -- 集合存在Shanghai元素，则代表只能修改分数为47，所以更新分数<br>zadd china 47 Hebei<br>    -- 添加一个分数为47的Hebei，此时的元素和47的Shanghai在一起<br>zadd china xx 9999 Huoxing<br>    -- 添加一个Huoxing元素并分数为9999，但是被xx修饰，表示只能更新不能添加，所有这个无法添加<br>zadd china xx  -9 Anhui<br>    -- 携带xx修饰，代表更新，此时存在Anhui这个元素并把分数修改了-9，（修改只能通过元素改分数，不能通过分数改元素）<br>zadd china nx 9999 Anhui<br>    -- 集合存在Anhui元素，则代表只能修改分数为9999，可是命令被nx修饰表示只能添加不能修改，所有修改失败<br>zadd china nx 9999 Dashijie<br>    -- 添加元素Dashijie，并且设置分数为9999，此时命令被nx修饰，表示可以元素添加，但不可以更新<br>    <br>zadd china xx lt 9998 Dashijie<br>    -- 携带xx只能表示修改，并且携带lt修改，表示待修改的元素分数只能比当前的分数小才可以，原9999改为现在9998<br>    -- 修改负数时，若当前分数为-10，则我们只能修改范围 -11 ~ -∞<br>    <br>zadd china xx gt 9999 Dashijie<br>    -- 携带gt则代表修改分数只能比元素当前的分数大才可以<br>    -- 修改负数时，若当前分数为-10，则我们只能修改范围 -9 ~ +∞<br>    <br>zadd china ch 25 Beijing 30 Henan 5555 Heilongjiang<br>    -- 携带ch修饰，表示添加成功、修改成功的次数会在返回值中返回<br>    <br>zadd china incr -2 Anhui<br>    -- 此时Anhui为-9 ；设置步长为-2 最终更新并返回-11<br>    -- 携带incr修饰，表示和zincrby命令相同，此时source代表累加的步长，member为改变元素<br></code></pre></td></tr></table></figure>



<p><strong>2.查询命令</strong></p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：zrange key min max [byscore|bylex] [rev] [limit offset count] [withscores]<br><br>说明：返回有序集中，指定区间内的成员，其中成员查询可以按照下标，分数，元素来获取指定范围<br>    key：要查询的有序集合<br>    min：查询范围的最小值，按照不同的方式写不同的最小值<br>        下标查询写下标值（默认）,分数查询写分数值（设置byscore），元素查询写元素值（设置bylex）<br>    max：查询范围的最大值，按照不同的方式写不同的最大值<br>        下标查询写下标值（默认）,分数查询写分数值（设置byscore），元素查询写元素值（设置bylex）<br>    [byscore|bylex]：<br>        byscore：按照分数排序，此时查询时只会按照分数的范围查询，切记不能写字符啥的<br>            -inf +inf byscore 代表查询分数在负无穷 ~ 正无穷<br>            -20 20    byscore 代表查询分数在 -20 ~ 20之间的元素(包含-20和20)<br>            10 (20    byscore 代表查询分数在 10 ~ 20之间的元素(包含10，不包含20)<br>        bylex：按照元素属性ASCLL来排序，此时查询时只会按照元素范围查询<br>            - + bylex       代表查询元素范围为全部<br>            - [cc bylex     代表从开头查询到元素 cc 位置（包含cc）<br>            [aa [dd bylex   代表查询元素为aa ~ dd之间的范围（包含aa和dd）<br>            (cc [ff bylex   代表查询元素为cc ~ ff之间的范围（包含ff，不包含cc）<br>            (aa (ee bylex   代表查询元素为aa ~ ee之间的范围（不包含aa和ee）<br>    [rev]：设置倒序排列，这时候我们就得注意写最大值和最小值要反过来写<br>    [limit offset count]：筛选后的结果排序<br>        offset：起始位（0下标开始数）<br>        count：查询元素个数<br>    [withscores]：最终查询结果显示分数，但是只适用于byscore查询和默认下标查询<br>    注：下标查询时是默认的，不用写[byscore|bylex]，下面写-1，代表右边第一个，所有0 -1代表查询全部<br>    注：若某个有序集合使用元素查询时（bylex），那么我推荐你最好使用分数都是相同的有序集合！因为分数会打乱原有我们添加的顺序，下面举个普通例子：<br>          执行命令=&gt;：zadd newKey 6 aa 3 bb 4 cc<br>          有序集合存储元素变为=&gt;：3 bb 4 cc 6 aa<br>          它会按照分数排序了，这样按照元素获取范围就不准了，如获取[aa [bb bylex<br>          127.0.0.1:6379&gt; zrange newKey 0 -1<br>          1) &quot;bb&quot;<br>          2) &quot;cc&quot;<br>          3) &quot;aa&quot;<br>          127.0.0.1:6379&gt; zrange newKey [aa [cc bylex<br>          1) &quot;bb&quot;<br>          2) &quot;cc&quot;<br>          3) &quot;aa&quot;<br>          127.0.0.1:6379&gt; zrange newKey [aa [bb bylex<br>          1) &quot;bb&quot;<br>          此时会发现[aa [bb bylex 为什么没有aa呢？因为分数打乱了顺序。<br>          其实用到元素查询那么有序集合里面的每个元素分数都是相同的，添加相同分数，元素会强行按照字典ASCII进行排序<br>          执行命令=&gt;：zadd newKey1 0 bb 0 aa 0 cc<br>          执行时插入时遇到分数相同的则会对元素的ASCII排序<br>          127.0.0.1:6379&gt; zrange newKey1 0 -1<br>          1) &quot;aa&quot;<br>          2) &quot;bb&quot;<br>          3) &quot;cc&quot;<br>          127.0.0.1:6379&gt; zrange newKey1 [aa [bb bylex<br>          1) &quot;aa&quot;<br>          2) &quot;bb&quot;<br>          127.0.0.1:6379&gt;<br>        再说了，使用bylex元素查询我们也不能指定withscores<br><br></code></pre></td></tr></table></figure>

<p><strong>zrange命令使用案例</strong></p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs txt">zrange myzsetA 0 -1<br>          -- 查询全部不带分数标识（值按照分数的从小到大升序返回）<br>      zrange myzsetA 0 -1 withscores<br>          -- 查询全部并携带分数标识<br>      zrange myzsetA -inf +inf byscore withscores<br>          -- 按照分数查询全部<br>      zrange myzsetA (-5 (20 byscore withscores<br>          -- 按照分数查询分数在-5 ~ 20之间（不包含-5和20）<br>      zrange myzsetA -5 (20 byscore withscores<br>          -- 按照分数查询分数在-5 ~ 20之间（包含-5，不包含20）<br>      zrange myzsetA [aa (dd bylex<br>          -- 按照元素查询在aa ~ dd之间（包含aa，不包含dd）<br>          -- 注：在一些特殊字符使用 ( 不包含可能会不准确 ，如 (##<br>          -- 注：元素查询是不可以携带withscores<br>      zrange myzsetA +inf -inf byscore rev withscores<br>          -- 倒序查询，此时我的min和max是反过来写的<br><br></code></pre></td></tr></table></figure>

<p>（2）计算numkeys个有序集合的交集</p>
<figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs xquery">语法：zinter numkeys<span class="hljs-built_in"> key</span> <span class="hljs-built_in">[key</span> ...] [weights weight [weight ...]] [aggregate<span class="hljs-built_in"> sum</span><span class="hljs-built_in">|min</span><span class="hljs-built_in">|max</span>] [withscores]<br>说明：计算numkeys个有序集合的交集（就是把几个相同元素的分数进行处理）<br>    numkeys：计算交集<span class="hljs-built_in">的key</span>个数<br>   <span class="hljs-built_in"> key</span> | <span class="hljs-built_in">[key</span> ...]：设置要处理交集的有序集合，按照我们给出的numkeys写指定个数<span class="hljs-built_in">的key</span><br>    [weights weight [weight ...]]：权重计算（乘法因子）；要设置权重的话，则有几<span class="hljs-built_in">个key</span>就得写几个权重值<br>        若key1 key2 weights <span class="hljs-number">10</span> <span class="hljs-number">15</span> 说明：第一<span class="hljs-built_in">个key</span>里面的全部分数要乘于<span class="hljs-number">10</span>，第二<span class="hljs-built_in">个key</span>的全部分数乘于<span class="hljs-number">15</span><br>        注：此属性在交集、并集计算中都存在，只要是符合[交集|并集]的才会计算并返回给客户端<br>    [aggregate<span class="hljs-built_in"> sum</span><span class="hljs-built_in">|min</span><span class="hljs-built_in">|max</span>]：你可以指定交集、并集的结果集的聚合方式<br>        注：指<span class="hljs-built_in">定sum</span>（默认）则交集的元素的分数结合，若指<span class="hljs-built_in">定max</span>，则会选择最大的作为交集的分数<br>    [withscores]：显示分数<br></code></pre></td></tr></table></figure>

<p>演示</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs cmd"><span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; zinter <span class="hljs-number">2</span> zsetA zsetB withscores<br>        <span class="hljs-number">1</span>) &quot;zhangsan&quot;<br>        <span class="hljs-number">2</span>) &quot;<span class="hljs-number">25</span>&quot;<br>        <span class="hljs-number">3</span>) &quot;mazi&quot;<br>        <span class="hljs-number">4</span>) &quot;<span class="hljs-number">30</span>&quot;<br>        <span class="hljs-number">5</span>) &quot;lisi&quot;<br>        <span class="hljs-number">6</span>) &quot;<span class="hljs-number">35</span>&quot;<br>        <span class="hljs-number">7</span>) &quot;babao&quot;<br>        <span class="hljs-number">8</span>) &quot;<span class="hljs-number">53</span>&quot;<br>            -- 设置两个有序集合的交集，默认分数是结合相加<br>        <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; zinter <span class="hljs-number">2</span> zsetA zsetB aggregate max withscores<br>        <span class="hljs-number">1</span>) &quot;mazi&quot;<br>        <span class="hljs-number">2</span>) &quot;<span class="hljs-number">15</span>&quot;<br>        <span class="hljs-number">3</span>) &quot;zhangsan&quot;<br>        <span class="hljs-number">4</span>) &quot;<span class="hljs-number">20</span>&quot;<br>        <span class="hljs-number">5</span>) &quot;lisi&quot;<br>        <span class="hljs-number">6</span>) &quot;<span class="hljs-number">25</span>&quot;<br>        <span class="hljs-number">7</span>) &quot;babao&quot;<br>        <span class="hljs-number">8</span>) &quot;<span class="hljs-number">33</span>&quot;<br>             -- 设置两个有序集合的交集，并且交集的分数是取最大的元素分数<br>        <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; zinter <span class="hljs-number">2</span> zsetA zsetB weights <span class="hljs-number">100</span> <span class="hljs-number">200</span> aggregate sum withscores<br>        <span class="hljs-number">1</span>) &quot;zhangsan&quot;<br>        <span class="hljs-number">2</span>) &quot;<span class="hljs-number">3000</span>&quot;<br>        <span class="hljs-number">3</span>) &quot;lisi&quot;<br>        <span class="hljs-number">4</span>) &quot;<span class="hljs-number">4500</span>&quot;<br>        <span class="hljs-number">5</span>) &quot;mazi&quot;<br>        <span class="hljs-number">6</span>) &quot;<span class="hljs-number">4500</span>&quot;<br>        <span class="hljs-number">7</span>) &quot;babao&quot;<br>        <span class="hljs-number">8</span>) &quot;<span class="hljs-number">7300</span>&quot;<br>            -- 设置两个有序集合的交集，并且交集的分数是相加，<br>            -- 还设置权重加载因子，zsetA每个分数*<span class="hljs-number">100</span>；zsetB每个分数*<span class="hljs-number">200</span><br>        <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; zinter <span class="hljs-number">1</span> zsetB weights <span class="hljs-number">1000</span> aggregate sum withscores<br>        <span class="hljs-number">1</span>) &quot;zhangsan&quot;<br>        <span class="hljs-number">2</span>) &quot;<span class="hljs-number">5000</span>&quot;<br>        <span class="hljs-number">3</span>) &quot;lisi&quot;<br>        <span class="hljs-number">4</span>) &quot;<span class="hljs-number">10000</span>&quot;<br>        <span class="hljs-number">5</span>) &quot;mazi&quot;<br>        <span class="hljs-number">6</span>) &quot;<span class="hljs-number">15000</span>&quot;<br>        <span class="hljs-number">7</span>) &quot;babao&quot;<br>        <span class="hljs-number">8</span>) &quot;<span class="hljs-number">20000</span>&quot;<br>            -- 一个有序集合的并集，就是展示自己全部元素<br></code></pre></td></tr></table></figure>



<p>3.删除命令</p>
<figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs txt">语法：zrem key member [member ...]<br>说明：用于从有序集合key中删除指定的多个成员member。如果member不存在则被忽略<br><br>语法：zremrangebyrank key start stop<br>说明：移除有序集key中，指定排名(rank)区间start和stop内的所有成员，下标参数start和stop都是从0开始计数<br>    注：按照下标删除元素<br>    zremrangebyrank myzsetA 0 -2 -- 删除从左边开始到右边倒数第二个的范围元素<br><br>语法：zremrangebylex key min max<br>说明：删除成员名称按字典由低到高排序介于min和max之间的所有成员；按照元素删除的话推荐在分数相同<br>     的情况下使用，因为每次添加带有不同分数的元素会找到自己的位置插入添加，不是按照字典由低到高插入添加<br>     所有我们后期删除时结果由于分数的影像导致删除不准确<br>    注：按照元素删除元素<br>    zremrangebylex myzsetA [aa (kk -- 删除元素在aa ~ kk 之间的（包括aa，不包括kk）<br><br>语法：zremrangebyscore key min max<br>说明：移除有序集key中，所有score值介于min和max之间(包括等于min或max)的成员<br>    注：按照分数删除元素<br>    zremrangebyscore myzsetA (-2 (30    -- 根据分数删除指定范围的元素（不包含-2和30）<br><br>语法：zpopmax key [count]<br>说明：删除分数最高的count个元素，如果未指定count则默认为1 ，删除会按照排序从高到低删除指定count个元素<br><br>语法：zpopmin key [count]<br>说明：删除分数最低的count个元素，如果未指定count则默认为1 ，删除会按照排序从低到高删除指定count个元素<br><br>语法：bzpopmax key [key ...] timeout<br>说明：和zpopmax一样，只不过是阻塞删除（若没有指定集合或元素则等待被创建），一次性删除一个，超时就自动退出<br><br>语法：bzpopmin key [key ...] timeout<br>说明：和zpopmin一样，只不过是阻塞删除（若没有指定集合或元素则等待被创建），一次性删除一个，超时就自动退出<br></code></pre></td></tr></table></figure>



<p>4.其他命令</p>
<figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs maxima">语法：zcount <span class="hljs-built_in">key</span> <span class="hljs-built_in">min</span> <span class="hljs-built_in">max</span><br>说明：返回有序集<span class="hljs-built_in">key</span>中，score值在<span class="hljs-built_in">min</span>和<span class="hljs-built_in">max</span>之间(<span class="hljs-built_in">min</span>&lt;=score&gt;=<span class="hljs-built_in">max</span>)的成员的数量<br>    <span class="hljs-built_in">min</span>：最小值，可以使用 -<span class="hljs-literal">inf</span> 代表负无穷大，默认是包含最小值，source&gt;=<span class="hljs-built_in">min</span>；若不包含可在值前面添加 &#x27;(&#x27; 如 (-<span class="hljs-number">20</span><br>    <span class="hljs-built_in">max</span>：最大值，可以使用 +<span class="hljs-literal">inf</span> 代表正无穷大，默认是包含最大值，source&lt;=<span class="hljs-built_in">max</span>；若不包含可在值前面添加 &#x27;(&#x27; 如 (<span class="hljs-number">20</span><br>    举例：<br>        zcount myzsetA -<span class="hljs-literal">inf</span> +<span class="hljs-literal">inf</span>    -- 查询有序集合里的全部成员数量<br>        zcount myzsetA -<span class="hljs-number">20</span> <span class="hljs-number">15</span>       -- 查询成员分数在-<span class="hljs-number">20</span> ~ <span class="hljs-number">15</span> 之间，包含-<span class="hljs-number">20</span>和<span class="hljs-number">15</span><br>        zcount myzsetA (-<span class="hljs-number">20</span> (<span class="hljs-number">20</span>     -- 查询成员分数在-<span class="hljs-number">20</span> ~ <span class="hljs-number">20</span> 之间，不包含-<span class="hljs-number">20</span>和<span class="hljs-number">20</span><br><br>语法：zincrby <span class="hljs-built_in">key</span> increment <span class="hljs-built_in">member</span><br>说明：为有序集<span class="hljs-built_in">key</span>的成员<span class="hljs-built_in">member</span>的score值加上增量increment<br>    注：若<span class="hljs-built_in">key</span>不存在，或者<span class="hljs-built_in">member</span>不是当前<span class="hljs-built_in">key</span>成员，则此命令自动转换为zadd命令<br>    zincrby chinas <span class="hljs-number">4</span> Anhui  -- 在有序集合上的Anhui元素分数增量 +<span class="hljs-number">4</span><br><br></code></pre></td></tr></table></figure>



<h2 id="5-Redis-持久化"><a href="#5-Redis-持久化" class="headerlink" title="5. Redis 持久化"></a>5. Redis 持久化</h2><p><strong>持久化的功能：</strong>Redis是内存数据库，数据都是存储在内存中，为了避免进程退出导致数据的永久丢失，需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘；当下次Redis重启时，利用持久化文件实现数据恢复。除此之外，为了进行灾难备份，可以将持久化文件拷贝到一个远程位置。</p>
<p>redis提供了两种持久化的方式，分别是RDB（Redis DataBase）和AOF（Append Only File）。</p>
<ol>
<li><strong>RDB持久化方式</strong><br>RDB（Redis DataBase）持久化是在设置的某个时间点内，将当前进程中的数据生成快照(Snapshot)保存到硬盘(因此也称作快照持久化)，保存的文件后缀是rdb；当Redis重新启动时，会自动读取快照文件恢复数据；也可以通过 save 和 bgsave 命令实现手动持久化</li>
</ol>
<p>　   注：RDB方式是将我们存储的 数据 保存到硬盘上</p>
<ol start="2">
<li><strong>AOF持久化方式</strong><br>　　AOF（append only file）持久化：以独立日志的方式记录每次增删改命令，重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式。当我们下次重启Redis服务时会先执行AOF文件来恢复数据</li>
</ol>
<p>　   注：RDB方式是将我们存储的 增删改命令 保存到硬盘上</p>
<h3 id="5-1-RDB"><a href="#5-1-RDB" class="headerlink" title="5.1 RDB"></a>5.1 RDB</h3><p>RDB方式，是将redis某一时刻的数据持久化到磁盘中，是一种快照式的持久化方法。</p>
<p>redis在进行数据持久化的过程中，会先将数据写入到一个临时文件中，待持久化过程都结束了，才会用这个临时文件替换上次持久化好的文件。正是这种特性，可以随时来进行备份，因为快照文件总是完整可用的。</p>
<p><strong>对于RDB方式，redis会单独创建（fork）一个子进程来进行持久化，而主进程是不会进行任何IO操作的，这样就确保了redis极高的性能。</strong></p>
<p>RDB文件紧凑，体积小，网络传输快，适合全量复制；恢复速度比AOF快很多。当然，与AOF相比，RDB最重要的优点之一是对性能的影响相对较小。</p>
<p>如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那RDB方式要比AOF方式更加的高效。</p>
<p>虽然RDB有不少优点，但它的缺点也是不容忽视的。<strong>RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化，因为即使每5分钟都持久化一次，</strong>当redis故障时，仍然会有近5分钟的数据丢失，而在数据越来越重要的今天，数据的大量丢失很多时候是无法接受的，因此AOF持久化成为主流。</p>
<h4 id="5-1-1-RDB持久化触发方式"><a href="#5-1-1-RDB持久化触发方式" class="headerlink" title="5.1.1 RDB持久化触发方式"></a>5.1.1 RDB持久化触发方式</h4><p><strong>手动触发：</strong><br>    <strong>save 命令：</strong><br>        执行此命令会阻塞当前Redis服务器，直到持久化完成，生产环境一般禁止使用（因为会阻塞其它客户端的访问操作）。<br>    <strong>bgsave 命令：</strong><br>        该触发方式会fork一个子进程，由子进程负责持久化过程，因此阻塞只会发生在fork子进程的时候。</p>
<p><strong>自动触发：</strong><br>    ① 根据我们在配置文件配置的 save m n 的规则来自动触发（后面介绍）<br>    ② 执行退出shutdown命令时会自动触发（前提是我们配置的持久化机制为RDB）<br>        执行退出shutdown nosave命令时不会自动触发<br>    ③ 执行flushAll会自动触发持久化（但是没有数据，只有一个空的.rdb文件）<br>        执行flushdb不会触发持久化<br>    ④ 执行debug reload会自动触发持久化</p>
<p><strong>bgsave 命令执行过程</strong></p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211212140800078-2065414849.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>①：执行bgsave命令，Redis父进程判断当前是否存在正在执行的子进程，如RDB/AOF子进程，如果存在bgsave命令直接返回。<br>②：父进程执行fork操作创建子进程，fork操作过程中父进程会阻塞，通过info stats命令查看latest_fork_usec选项，可以获取最近一个fork操作的耗时，单位为微秒。<br>③：父进程fork完成后，bgsave命令返回“Background saving started”信息并不再阻塞父进程，可以继续响应其他命令。<br>④：子进程创建RDB文件，根据父进程内存生成临时快照文件，完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的时间，对应info统计的rdb_last_save_time选项。<br>⑤：进程发送信号给父进程表示完成，父进程更新统计信息。</p>
<p><strong>save m n自动持久化原理</strong></p>
<p>Redis的save m n，是通过 <strong>serverCron</strong> 函数、**dirty **计数器和 <strong>lastsave</strong> 时间戳来实现的。<br>serverCron：<br>    是Redis服务器的周期性操作函数，默认每隔100ms执行一次；该函数对服务器的状态进行维护，其中一项工作就是检查 save m n 配置的条件是否满足，如果满足就执行bgsave。</p>
<p>dirty：<br>    计数器是Redis服务器维持的一个状态，记录了上一次执行bgsave/save命令后，服务器状态进行了多少次修改(包括增删改)；而当save/bgsave执行完成后，会将dirty重新置为0。例如：如果Redis执行了set name jack，则dirty值会+1；如果执行了sadd myset v1 v2 v3，则dirty值会+3；</p>
<p>（注意dirty记录的是服务器进行了多少次修改，而不是客户端执行了多少修改数据的命令，一个服务器可能有多个客户端）。</p>
<p>lastsave时间戳也是Redis服务器维持的一个状态，记录的是上一次成功执行save/bgsave的时间。</p>
<p>save m n的原理如下：<br>    每隔100ms，执行serverCron函数；在serverCron函数中，遍历save m n配置的保存条件，只要有一个条件满足，就进行bgsave。对于每一个save m n条件，只有下面两条同时满足时才算满足：<br>lastsave &gt; m<br>dirty &gt;= n</p>
<h4 id="5-1-2-RDB配置文件"><a href="#5-1-2-RDB配置文件" class="headerlink" title="5.1.2 RDB配置文件"></a>5.1.2 RDB配置文件</h4><p>配置文件在redis的安装目录下的config文件中</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs conf">Redis常用配置：<br># 默认情况下Redis将自动保存数据：<br># 3600  秒（1 小时）后，如果至少更改了  1  个键<br># 300   秒（5 分钟）后，如果至少更改了 100 个键<br># 60    秒（1 分钟）后，如果至少更改了10000个键<br># 您可以通过取消注释以下三行来显式设置这些（可自定义save 秒 键）<br>save 3600 1<br>save 300 100<br>save 60 10000<br><br># 当bgsave出现错误时，Redis是否停止执行写命令；<br>#   yes：则当硬盘出现问题时，可以及时发现，避免数据的大量丢失；<br>#   no：则Redis无视bgsave的错误继续执行写命令，当对Redis服务器的系统(尤其是硬盘)使用了监控时，该选项考虑设置为no<br>stop-writes-on-bgsave-error yes<br><br># 是否开启RDB文件压缩<br>rdbcompression yes<br><br># 是否开启RDB文件的校验，在写入文件和读取文件时都起作用；关闭checksum在写入文件和启动文件时大约能带来10%的性能提升，但是数据损坏时无法发现<br>rdbchecksum yes<br><br># 存储后的RDB文件名 默认dump.rdb<br>dbfilename dump.rdb<br><br># 是否删除主从复制时创建的RDB文件（在没有开启持久化的实例中，最好将其删除）<br>rdb-del-sync-files no<br><br># 存储持久化文件的目录 以上使用“dbfilename”配置指令，附加文件也将在此目录中创建<br>dir ./<br></code></pre></td></tr></table></figure>



<h4 id="5-1-3-基本操作演示"><a href="#5-1-3-基本操作演示" class="headerlink" title="5.1.3 基本操作演示"></a>5.1.3 基本操作演示</h4><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs cmd"># 执行save<br>客户端：<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; save<br>    OK<br>服务端：<br>     <span class="hljs-number">910</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">14</span>:<span class="hljs-number">59</span>:<span class="hljs-number">31</span>.<span class="hljs-number">373</span> * DB saved on disk<br><br># 执行bgsave<br>客户端：<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; bgsave<br>    Background saving started<br>服务端：<br>    <span class="hljs-number">1</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">00</span>:<span class="hljs-number">44</span>.<span class="hljs-number">755</span> * Background saving started by pid <span class="hljs-number">913</span><br>    <span class="hljs-number">26</span>:C <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">00</span>:<span class="hljs-number">44</span>.<span class="hljs-number">777</span> * DB saved on disk<br>    <span class="hljs-number">1</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">00</span>:<span class="hljs-number">44</span>.<span class="hljs-number">825</span> * Background saving terminated with success<br><br># 执行flushall<br>客户端：<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; FLUSHALL<br>    OK<br>服务端：<br>    <span class="hljs-number">1</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">01</span>:<span class="hljs-number">55</span>.<span class="hljs-number">217</span> * DB saved on disk<br><br># 自动存储（save <span class="hljs-number">30</span> <span class="hljs-number">2</span>）当<span class="hljs-number">30</span>秒内执行<span class="hljs-number">2</span>次命令则会在<span class="hljs-number">30</span>秒后执行自动持久化RDB<br>客户端：<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; <span class="hljs-built_in">set</span> name zhangsan<br>    OK<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; <span class="hljs-built_in">set</span> age <span class="hljs-number">22</span><br>    OK<br>服务端：<br>    <span class="hljs-number">245</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">09</span>.<span class="hljs-number">045</span> * <span class="hljs-number">2</span> changes <span class="hljs-keyword">in</span> <span class="hljs-number">30</span> seconds. Saving...<br>    <span class="hljs-number">245</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">09</span>.<span class="hljs-number">092</span> * Background saving started by pid <span class="hljs-number">247</span><br>    <span class="hljs-number">247</span>:C <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">09</span>.<span class="hljs-number">118</span> * DB saved on disk<br>    <span class="hljs-number">245</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">09</span>.<span class="hljs-number">200</span> * Background saving terminated with success<br><br># 在有RDB文件的情况下打开Redis服务<br>服务端：<br>    <span class="hljs-number">1240</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">35</span>.<span class="hljs-number">975</span> * Loading RDB produced by version <span class="hljs-number">6</span>.<span class="hljs-number">2</span>.<span class="hljs-number">5</span><br>    <span class="hljs-number">1240</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">35</span>.<span class="hljs-number">975</span> * RDB age <span class="hljs-number">26</span> seconds<br>    <span class="hljs-number">1240</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">35</span>.<span class="hljs-number">975</span> * RDB memory usage when created <span class="hljs-number">0</span>.<span class="hljs-number">48</span> Mb<br>    <span class="hljs-number">1240</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">35</span>.<span class="hljs-number">976</span> * DB loaded from disk: <span class="hljs-number">0</span>.<span class="hljs-number">001</span> seconds<br>    <span class="hljs-number">1240</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">15</span>:<span class="hljs-number">06</span>:<span class="hljs-number">35</span>.<span class="hljs-number">976</span> * Ready to accept connections<br></code></pre></td></tr></table></figure>



<p>注：通过在redis里面config set 进行修改的配置，重启服务后，配置会恢复到配置文件中的配置，若想永久修改则去配置文件更改。</p>
<h3 id="5-2-AOF持久化"><a href="#5-2-AOF持久化" class="headerlink" title="5.2 AOF持久化"></a>5.2 AOF持久化</h3><p>AOF(append only file)持久化：以独立日志的方式记录每次写命令，重启时再重新执行AOF文件中命令达到恢复数据的目的。与RDB相比可以简单理解为由记录数据改为记录数据产生的变化。</p>
<p>与RDB持久化相对应，AOF的优点在于支持秒级持久化、兼容性好，缺点是文件大、恢复速度慢、对性能影响大。</p>
<p>AOF的主要作用是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式。</p>
<h4 id="5-2-1-AOF持久化执行方式"><a href="#5-2-1-AOF持久化执行方式" class="headerlink" title="5.2.1 AOF持久化执行方式"></a>5.2.1 AOF持久化执行方式</h4><p>默认都是自动触发RDB持久化方式的，若想AOF持久化方式我们首先得去配置文件开启此功能；<strong>appendonly yes</strong> 默认是未开启；而AOF持久化的文件名则需要通过 <strong>appendfilename</strong> 来配置，默认AOF文件名称为<strong>appendonly.aof</strong>；保存路径同RDB持久化方式一致，通过dir配置指定。</p>
<p><strong>AOF的执行方式是：只要执行增删改命令就会执行一次追加操作，往 appendonly.aof 文件内追加增删改命令</strong></p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211212171405687-941010289.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p>流程：<br>①：所有的增删改命令会追加到aof_buf（AOF缓冲区）中。<br>②：根据不同的同步策略将aof_buf中的内容同步到硬盘。<br>③：随着AOF文件越来越大，需要定期对AOF文件进行重写，达到压缩的目的。<br>④：当Redis服务器重启时，可以加载AOF文件进行数据恢复。</p>
<p>AOF命令追加方式（append）：</p>
<p>Redis先将写命令追加到缓冲区，而不是直接写入文件，主要是为了避免每次有写命令都直接写入硬盘，导致硬盘IO成为Redis负载的瓶颈。<br>命令追加的格式是Redis命令请求的协议格式，它是一种纯文本格式，具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点。<br>在AOF文件中，除了用于指定数据库的select命令（如select 0 为选中0号数据库）是由Redis添加的，其他都是客户端发送来的写命令。</p>
<h4 id="5-2-2-AOF配置文件"><a href="#5-2-2-AOF配置文件" class="headerlink" title="5.2.2 AOF配置文件"></a>5.2.2 AOF配置文件</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><code class="hljs conf">appendonly yes<br><br># 是否开启AOF日志记录，默认redis使用的是rdb方式持久化，这种方式在许多应用中已经足够用了，但是redis如果中途宕机，会导致可能有几分钟的数据丢失(取决于dump数据的间隔时间)，根据save来策略进行持久化，<br># Append Only File是另一种持久化方式， 可以提供更好的持久化特性，Redis会把每次写入的数据在接收后都写入appendonly.aof文件，每次启动时Redis都会先把这个文件的数据读入内存里，先忽略RDB文件。默认不启用此功能<br><br><br># 文本文件AOF的文件名，存放目录在dir配置中配置<br>appendfilename &quot;appendonly.aof&quot;<br><br><br># appendfsync always<br>appendfsync everysec<br># appendfsync no<br><br># aof持久化策略的配置（appendfsync属性说明）<br># always：<br>#   命令写入aof_buf后立即调用系统fsync操作同步到AOF文件，fsync完成后线程返回。这种情况下，每次增删改命令都要同步到AOF文件，这样硬盘IO会成为性能瓶颈，Redis只能支持大约几百TPS写入，严重降低了Redis的性能；即便是使用固态硬盘（SSD），每秒大约也只能处理几万个命令，而且会大大降低SSD的寿命。<br>#   总结：每次增删改都执行fsync，以保证数据同步到磁盘,安全性高,但性能较差<br># no：<br>#   命令写入aof_buf后调用系统write操作，不对AOF文件做fsync同步；同步由操作系统负责，通常同步周期为30秒。这种情况下，文件同步的时间不可控，且缓冲区中堆积的数据会很多，数据安全性无法保证。<br>#   总结：由操作系统保证数据同步到磁盘,Linux的默认fsync策略是30秒，最多会丢失30s的数据<br># everysec：<br>#   命令写入aof_buf后调用系统write操作，write完成后线程返回；fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中，是性能和数据安全性的平衡，因此是Redis的默认配置，也是推荐的配置。<br><br>no-appendfsync-on-rewrite no<br><br># 在AOF rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间<br># no：<br>#   表示&quot;不暂缓&quot;,新的aof记录仍然会被立即同步到磁盘，是最安全的方式，不会丢失数据，但是要忍受阻塞的问题<br># yes：<br>#   相当于将appendfsync设置为no，这说明并没有执行磁盘操作，只是写入了缓冲区，因此这样并不<br>#   会造成阻塞（因为没有竞争磁盘），但是如果这个时候redis挂掉，就会丢失数据。丢失多少数据呢？Linux的默认fsync策略是30秒，最多会丢失30s的数据,但由于yes性能较好而且会避免出现阻塞因此比较推荐rewrite， 即对aof文件进行整理,将空闲空间回收,从而可以减少恢复数据时间<br><br>auto-aof-rewrite-min-size 64mb<br><br># aof_current_size 和 aof_base_size 可以执行 info persistence 或直接 info<br># 执行AOF重写时，当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值；文件重写触发条件之一<br>auto-aof-rewrite-percentage 100<br># 执行AOF重写时，文件的最小体积，默认值为64MB。文件重写触发条件之一<br><br>aof-load-truncated yes<br># 是否加载由于某些原因导致的末尾异常的AOF文件(主进程被kill/断电等)，建议yes<br><br>aof-use-rdb-preamble yes<br># redis4.0新增RDB-AOF混合持久化格式，在开启了这个功能之后，AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容，<br># 其中RDB格式的内容用于记录已有的数据，而AOF格式的内容则用于记录最近发生了变化的数据，这样Redis就可以同时兼有RDB持<br># 久化和AOF持久化的优点（既能够快速地生成重写文件，也能够在出现问题时，快速地载入数据）,默认为no,即不启用此功能<br><br></code></pre></td></tr></table></figure>



<h4 id="5-2-3-AOF文件重写（rewrite）"><a href="#5-2-3-AOF文件重写（rewrite）" class="headerlink" title="5.2.3 AOF文件重写（rewrite）"></a>5.2.3 AOF文件重写（rewrite）</h4><p>​    随着时间流逝，Redis服务器执行的写命令越来越多，AOF文件也会越来越大；过大的AOF文件不仅会影响服务器的正常运行，也会导致数据恢复需要的时间过长。<br>​    文件重写是指定期重写AOF文件，减小AOF文件的体积。需要注意的是，AOF重写是把Redis进程内的数据转化为写命令，同步到新的AOF文件；不会对旧的AOF文件进行任何读取、写入操作!<br>​    关于文件重写需要注意的另一点是：对于AOF持久化来说，文件重写虽然是强烈推荐的，但并不是必须的；即使没有文件重写，数据也可以被持久化并在Redis启动的时候导入；因此在一些实现中，会关闭自动的文件重写，然后通过定时任务在每天的某一时刻定时执行。</p>
<p>文件重写之所以能够压缩AOF文件，原因在于：<br>    ①：过期的数据不再写入文件<br>    ②：无效的命令不再写入文件：如有些数据被重复设值(set name jack, set name tom)、<br>        有些数据被删除了(sadd myset v1, del myset)等等<br>    ③：多条命令可以合并为一个：如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3<br>        不过为了防止单条命令过大造成客户端缓冲区溢出，对于list、set、hash、zset类型的key，并不一定只使用一条命令；而是以某个常量为界将命令拆分为多条。<br>        这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义，不可更改，3.0版本中值是64。<br>通过上述内容可以看出，由于重写后AOF执行的命令减少了，文件重写既可以减少文件占用的空间，也可以加快恢复速度。</p>
<p><strong>文件重写流程</strong></p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/1670015-20211213175231490-789691828.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs mipsasm">文件重写的流程，注意点：<br>    ①：重写由父进程fork出一个子进程进行持久化<br>    ②：重写期间Redis执行的写命令，需要追加到新的AOF文件中，为此Redis引入了aof_rewrite_buf缓存<br><br>对照上图，文件自动重写的流程如下：<br>     <span class="hljs-number">1</span>：Redis父进程首先判断当前是否存在正在执行 <span class="hljs-keyword">bgsave/bgrewriteaof的子进程，如果存在则bgrewriteaof命令直接返回，</span><br><span class="hljs-keyword"></span>  如果存在<span class="hljs-keyword">bgsave命令则等bgsave执行完成后再执行，这个主要是基于性能方面的考虑。</span><br><span class="hljs-keyword"></span>  如果当前进程正在执行AOF（<span class="hljs-keyword">bgrewriteaof）重写，请求不执行并返回如下响应：</span><br><span class="hljs-keyword"></span>            ERR <span class="hljs-keyword">Background </span>append only file rewriting already in progress<br>  如果当前进程正在执行<span class="hljs-keyword">bgsave操作，重写命令延迟到bgsave完成之后再执行，返回如下响应：</span><br><span class="hljs-keyword"></span>            <span class="hljs-keyword">Background </span>append only file rewriting <span class="hljs-keyword">scheduled</span><br><span class="hljs-keyword"></span>            <br>     <span class="hljs-number">2</span>：父进程执行fork操作创建子进程，这个过程中父进程是阻塞的（开销等同于<span class="hljs-keyword">bgsave过程）</span><br><span class="hljs-keyword"></span> <br>     <span class="hljs-number">3</span>.<span class="hljs-number">1</span>：父进程fork后，<span class="hljs-keyword">bgrewriteaof命令返回”Background </span>append only file rewrite started”信息；同时不再阻塞父进程，并可以继续响应其它命令；所有增删改命令依然写入AOF缓冲区并根据appendfsync策略同步到硬盘，保证原有AOF机制正确性。<br> <br>	<span class="hljs-number">3</span>.<span class="hljs-number">2</span>：由于fork操作使用写时复制技术，子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令，因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据，防止新AOF文件生成期间丢失这部分数据。也就是说，<span class="hljs-keyword">bgrewriteaof执行期间Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。</span><br><span class="hljs-keyword"></span> <br>    <span class="hljs-number">4</span>：子进程根据内存快照，按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制默认为<span class="hljs-number">32</span>MB，防止单次刷盘数据过多造成硬盘阻塞。<br>    <br>    <span class="hljs-number">5</span>.<span class="hljs-number">1</span>：子进程写完新的AOF文件后，向父进程发信号，父进程更新统计信息，具体可以通过info persistence查看。<br>    <br>    <span class="hljs-number">5</span>.<span class="hljs-number">2</span>：父进程把AOF重写缓冲区的数据写入到新的AOF文件，这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。<br>    <br>    <span class="hljs-number">5</span>.<span class="hljs-number">3</span>：使用新的AOF文件替换老文件，完成AOF重写<br></code></pre></td></tr></table></figure>





<h4 id="5-2-4-文件重写的触发"><a href="#5-2-4-文件重写的触发" class="headerlink" title="5.2.4  文件重写的触发"></a>5.2.4  文件重写的触发</h4><p><strong>手动触发：</strong></p>
<p>直接调用bgrewriteaof命令，该命令的执行与bgsave有些类似，都是fork子进程进行具体工作，且都只有在fork时阻塞</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs cmd">执行手动触发AOF文件重写<br>客户端：<br>    <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; bgrewriteaof<br>    Background <span class="hljs-built_in">append</span> only file rewriting started<br>服务端：<br>    <span class="hljs-number">1751</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">401</span> * Background <span class="hljs-built_in">append</span> only file rewriting started by pid <span class="hljs-number">1753</span><br>    <span class="hljs-number">1751</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">736</span> * AOF rewrite child asks to stop sending diffs.<br>    <span class="hljs-number">1753</span>:C <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">752</span> * Parent agreed to stop sending diffs. Finalizing AOF...<br>    <span class="hljs-number">1753</span>:C <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">754</span> * Concatenating <span class="hljs-number">0</span>.<span class="hljs-number">00</span> MB of AOF diff received from parent.<br>    <span class="hljs-number">1753</span>:C <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">769</span> * SYNC <span class="hljs-built_in">append</span> only file rewrite performed<br>    <span class="hljs-number">1751</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">845</span> * Background AOF rewrite terminated with success<br>    <span class="hljs-number">1751</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">848</span> * Residual parent diff successfully flushed to the rewritten AOF (<span class="hljs-number">0</span>.<span class="hljs-number">00</span> MB)<br>    <span class="hljs-number">1751</span>:M <span class="hljs-number">22</span> Feb <span class="hljs-number">2023</span> <span class="hljs-number">20</span>:<span class="hljs-number">33</span>:<span class="hljs-number">45</span>.<span class="hljs-number">863</span> * Background AOF rewrite finished successfully<br></code></pre></td></tr></table></figure>



<p><strong>自动触发：</strong></p>
<p>自动触发是根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage配置参数，以及aof_current_size和aof_base_size状态确定触发时机。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs conf">auto-aof-rewrite-min-size：<br>    执行AOF重写时，文件的最小体积，默认值为64MB。<br>auto-aof-rewrite-percentage：<br>    执行AOF重写时，当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值<br>可以通过config get命令查看：<br>    127.0.0.1:6379[12]&gt; config get auto-aof-rewrite-percentage<br>    1) &quot;auto-aof-rewrite-percentage&quot;<br>    2) &quot;100&quot;　　-- 百分比 100%<br>    127.0.0.1:6379[12]&gt; config get auto-aof-rewrite-min-size<br>    1) &quot;auto-aof-rewrite-min-size&quot;<br>    2) &quot;67108864&quot;  -- 大小 64M<br></code></pre></td></tr></table></figure>



<h2 id="6-Redis可视化客户端"><a href="#6-Redis可视化客户端" class="headerlink" title="6. Redis可视化客户端"></a>6. Redis可视化客户端</h2><p>Redis Desktop Manager</p>
<p>redis安装完成后，使用命令行查看数据还是有点不方便。于是就诞生了可视化的工具去查看数据。Redis Desktop Manager就是其中一个。</p>
<p>1.下载</p>
<p><a target="_blank" rel="noopener" href="https://github.com/RedisInsight/RedisDesktopManager/releases/tag/0.9.3">https://github.com/RedisInsight/RedisDesktopManager/releases/tag/0.9.3</a></p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230222101213264.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230222101213264"></p>
<p>2.连接</p>
<p>3.redis数据库增删改查</p>
<h2 id="7-Jedis操作redis的五种数据类型"><a href="#7-Jedis操作redis的五种数据类型" class="headerlink" title="7. Jedis操作redis的五种数据类型"></a>7. Jedis操作redis的五种数据类型</h2><p>​    Jedis Client是Redis官网推荐的一个面向java客户端，库文件实现了对各类API进行封装调用。常用的还有redisson，jedis跟接近于原生的操作，而redisson跟适合用于分布式，提供了分布式锁，以及其他多种数据结构。</p>
<p>代码演示</p>
<h2 id="8-Springboot整合redis"><a href="#8-Springboot整合redis" class="headerlink" title="8. Springboot整合redis"></a>8. Springboot整合redis</h2><h3 id="1-基本介绍"><a href="#1-基本介绍" class="headerlink" title="1. 基本介绍"></a>1. 基本介绍</h3><ul>
<li>springboot在现在的版本中操作Redis数据库用到了lettuce，而不是Jedis，他们各有各的特点。</li>
<li>Jedis以Redis命令作为方法名称，学习成本低，简单实用。但是Jedis实例是线程不安全的，多线程环境下需要基于连接池来使用。</li>
<li>Lettuce是基于Netty实现的，支持同步、异步和响应式编程方式，并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。</li>
</ul>
<p><strong>SpringDataRedis</strong></p>
<ul>
<li>SpringDataRedis是Spring中数据操作的模块，包含对各种数据库的集成，其中对Redis的集成模块就叫做SpringDataRedis。</li>
<li>它提供了RedisTemplate统一API来操作Redis功能非常的多。</li>
</ul>
<p><strong>API的简单认识</strong></p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/af0600c3d67d4f1aa515367036ad82bf.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="在这里插入图片描述"></p>
<h3 id="2-无序列化方式"><a href="#2-无序列化方式" class="headerlink" title="2. 无序列化方式"></a>2. 无序列化方式</h3><p>1.引入依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.apache.commons<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>commons-pool2<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span> <br><span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span><br></code></pre></td></tr></table></figure>


<p>2.配置文件</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs yml"><span class="hljs-attr">server:</span><br>  <span class="hljs-attr">port:</span> <span class="hljs-number">8082</span><br><span class="hljs-attr">spring:</span><br>  <span class="hljs-attr">application:</span><br>    <span class="hljs-attr">name:</span> <span class="hljs-string">jedis-test-dataredis</span><br>  <span class="hljs-attr">redis:</span><br>    <span class="hljs-attr">host:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><br>    <span class="hljs-attr">port:</span> <span class="hljs-number">6379</span><br>    <span class="hljs-attr">database:</span> <span class="hljs-number">0</span><br>    <span class="hljs-attr">connect-timeout:</span> <span class="hljs-number">18000</span><br>    <span class="hljs-attr">password:</span> <span class="hljs-string">&quot;123456&quot;</span><br>    <span class="hljs-attr">lettuce:</span><br>      <span class="hljs-attr">pool:</span><br>        <span class="hljs-comment"># 最大阻塞等待时间，负数表示没有限制</span><br>        <span class="hljs-attr">max-wait:</span> <span class="hljs-number">-1</span><br>        <span class="hljs-comment"># 连接池中的最大空闲连接</span><br>        <span class="hljs-attr">max-idle:</span> <span class="hljs-number">5</span><br>        <span class="hljs-comment"># 连接池中的最小空闲连接</span><br>        <span class="hljs-attr">min-idle:</span> <span class="hljs-number">0</span><br>        <span class="hljs-comment"># 连接池中最大连接数，负数表示没有限制</span><br>        <span class="hljs-attr">max-active:</span> <span class="hljs-number">20</span><br></code></pre></td></tr></table></figure>

<p>3.存入redis</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RedisTest1</span> &#123;<br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> RedisTemplate redisTemplate;<br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testOne</span><span class="hljs-params">()</span> &#123;<br>        redisTemplate.opsForValue().set(<span class="hljs-string">&quot;name&quot;</span>,<span class="hljs-string">&quot;没有序列化name&quot;</span>);<br>        <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> (String) redisTemplate.opsForValue().get(<span class="hljs-string">&quot;name&quot;</span>);<br>        System.out.println(name); <span class="hljs-comment">//没有序列化name</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>4.Controller</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RestController</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">testController</span> &#123;<br>    <span class="hljs-meta">@Resource</span><br>    <span class="hljs-keyword">private</span> RedisTest1 redisTest1;<br>    <span class="hljs-meta">@RequestMapping(&quot;/test01&quot;)</span><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setKeys</span><span class="hljs-params">()</span> &#123;<br>        redisTest1.testOne();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>问题出现了：当我们使用Redis客户端查看刚刚存入redis的数据时，结果是这样的：</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223113531435.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230223113531435"></p>
<p>是因为在使用默认的对象<code>redisTemplate</code>时，默认的JdkSerializationRedisSerializer，会把value值序列化为byte类型，所以就出现了上图的结果。</p>
<h3 id="3-自定义序列化"><a href="#3-自定义序列化" class="headerlink" title="3. 自定义序列化"></a>3. 自定义序列化</h3><p><strong>序列化的定义</strong></p>
<p>序列化：把对象转化为可传输的字节序列过程称为序列化。</p>
<p>反序列化：把字节序列还原为对象的过程称为反序列化。</p>
<p><strong>为什么需要序列化</strong></p>
<p>序列化最终的目的是为了对象可以<strong>跨平台存储，和进行网络传输</strong>。而我们进行跨平台存储和网络传输的方式就是IO，而我们的IO支持的数据格式就是字节数组。</p>
<p>因为我们单方面的只把对象转成字节数组还不行，因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的，所以我们必须在把对象转成字节数组的时候就制定一种规则<strong>（序列化）</strong>，那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来<strong>（反序列化）</strong></p>
<p><strong>Redis序列化策略</strong></p>
<p>当我们的数据存储到Redis的时候，我们的键（key）和值（value）都是通过Spring提供的Serializer序列化到数据库的。对于redis操作，springboot进行了很好的封装，那就是spring-data-redis。提供了一个高度封装的 RedisTemplate类来进行一系列redis操作，连接池自动管理；同时将事务封装操作，交由容器进行处理。针对数据的”序列化和反序列化”，提供了多种策略。</p>
<p><strong>JdkSerializationRedisSerializer：</strong>POJO对象的存取场景，使用JDK本身序列化机制，将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作，最终redis-server中将存储字节序列。是目前默认的序列化策略。</p>
<p><strong>StringRedisSerializer：</strong>Key或者value为字符串的场景，根据指定的charset对数据的字节序列编码成string，是 “new String(bytes, charset)” 和 “string.getBytes(charset)” 的直接封装。是最轻量级和高效的策略。</p>
<p><strong>Jackson2JsonRedisSerializer：</strong> 使用Jackson库将对象序列化为JSON字符串。优点是速度快，序列化后的字符串短小精悍，不需要实现Serializable接口。但缺点也非常致命，那就是此类的构造函数中有一个类型参数，必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码，发现其只在反序列化过程中用到了类型信息。</p>
<p>没有@class或者@type类型属性，这种序列化方式可能会导致获取redis数据反序列化成POJO对象时候出错，导致反序列化失败。</p>
<p><strong>GenericJackson2JsonRedisSerializer：</strong>类似Jackson2JsonRedisSerializer，但使用时构造函数不用特定的类，序列化之后的内容，是存储了对象的class信息的。采用GenericJackson2JsonRedisSerializer序列化方式对于String、对象、对象数组、JSONObject、JSONArray的序列化反序列化操作都正常。</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223151012217.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230223151012217"></p>
<p>key和hashKey：推荐使用   StringRedisSerializer 简单的字符串序列化</p>
<p>hashValue：推荐使用   GenericJackson2JsonRedisSerializer</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">RedisConfig</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">CachingConfigurerSupport</span> &#123;<br><br>    <span class="hljs-meta">@Bean</span><br>    <span class="hljs-meta">@SuppressWarnings(&quot;all&quot;)</span><br>    <span class="hljs-keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="hljs-title function_">redisTemplate</span><span class="hljs-params">(RedisConnectionFactory redisConnectionFactory)</span> &#123;<br>        RedisTemplate&lt;String, Object&gt; redisTemplate = <span class="hljs-keyword">new</span> <span class="hljs-title class_">RedisTemplate</span>&lt;String, Object&gt;();<br>        <span class="hljs-type">StringRedisSerializer</span> <span class="hljs-variable">keySerializer</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">StringRedisSerializer</span>();<br>        <span class="hljs-comment">//alibaba的fastjson依赖</span><br>        <span class="hljs-comment">//GenericFastJsonRedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();</span><br><br>        GenericJackson2JsonRedisSerializer valueSerializer=<span class="hljs-keyword">new</span> <span class="hljs-title class_">GenericJackson2JsonRedisSerializer</span>();<br>        redisTemplate.setConnectionFactory(redisConnectionFactory);<br>        <span class="hljs-comment">// 序列化 key 和 value</span><br>        redisTemplate.setKeySerializer(keySerializer);<br>        redisTemplate.setValueSerializer(valueSerializer);<br>        redisTemplate.setHashKeySerializer(keySerializer);<br>        redisTemplate.setHashValueSerializer(valueSerializer);<br><br>        <span class="hljs-keyword">return</span> redisTemplate;<br>    &#125;<br></code></pre></td></tr></table></figure>

<p>在使用自定义的序列化策略之后，可以更加容易的进行开发</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230223145617900.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230223145617900"></p>
<h2 id="9-SpringBoot-Redis集成验证码代码分享"><a href="#9-SpringBoot-Redis集成验证码代码分享" class="headerlink" title="9.SpringBoot+Redis集成验证码代码分享"></a>9.SpringBoot+Redis集成验证码代码分享</h2><p>代码展示</p>
<h5 id=""><a href="#" class="headerlink" title=""></a></h5><h2 id="10-Redis缓存穿透和雪崩"><a href="#10-Redis缓存穿透和雪崩" class="headerlink" title="10. Redis缓存穿透和雪崩"></a>10. Redis缓存穿透和雪崩</h2><h3 id="10-1-缓存雪崩"><a href="#10-1-缓存雪崩" class="headerlink" title="10.1 缓存雪崩"></a>10.1 缓存雪崩</h3><p>缓存雪崩：大量应用请求无法在Redis缓存中进行处理，应用请求频繁访问数据库，导致数据库压力激增。</p>
<p>产生雪崩的两种情况：</p>
<ul>
<li>大量数据同时失效</li>
<li>Redis实例故障宕机</li>
</ul>
<p>缓存中有大量数据同时过期或者Redis实例故障宕机，导致大量请求无法得到处理。</p>
<ul>
<li>数据保存在缓存中，并设置了过期时间时，如果某一时刻，大量数据同时过期，此时，应用程序再访问这些数据的话，就会发生缓存缺失。</li>
<li>应用就会把请求发送给数据库，从数据库中读取数据。</li>
<li>如果应用的并发请求量很大，那么数据库的压力也就很大，这回进一步影响到数据库的其他业务请求处理，进而导致数据库崩溃。</li>
<li>一个Redis实例可以支持数万级别的请求处理吞吐量，而单个数据库只能支持数千级别的请求处理吞吐量，他们两个的处理能力可能相差了近乎十倍。一旦缓存雪崩，Redis缓存失效，那么数据库直接承受十倍请求压力，导致崩溃。</li>
</ul>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302144348559.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302144348559"></p>
<p>大量数据同时失效带来的缓存雪崩问题，两种解决方案：</p>
<ul>
<li>我们可以避免给大量数据设置相同的过期时间。 如果业务曾的确要求有数据同时失效，你可以用 EXPIRE命令给每个数据设置过期时间时，给这些数据的过期时间,给这些数据过期时间增加一些较小的随机数 不同数据的过期时间有差别，但是差别又不大，避免大量数据同时过期。 同时保证数据基本在相近时间失效，仍能满足业务需求。</li>
<li>除了微调过期时间，我们通过服务降级来应对缓存雪崩。</li>
</ul>
<p>服务降级：是指发生缓存雪崩，针对不同的数据采用不同的处理方式：</p>
<ul>
<li>当业务应用访问的是非核数据时候，暂时停止从缓存中查询这些数据，而是直接返回预定义信息或者空数据，等到Redis缓存实例重新恢复服务以后，再允许应用请求发送到缓存系统。</li>
<li>当业务应用访问的是核心数据，仍然允许查询缓存，如果缓存缺失也可以继续通过数据库读取。</li>
<li>事先预防，主从节点构建Redis缓存高可用集群，当Redis缓存的主节点故障宕机了，从节点切换为主节点，继续提供缓存服务避免缓存实例宕机造成的缓存雪崩问题。</li>
</ul>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302144621078.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302144621078"></p>
<h3 id="10-2-缓存击穿"><a href="#10-2-缓存击穿" class="headerlink" title="10.2 缓存击穿"></a>10.2 缓存击穿</h3><p>​     缓存击穿：针对某个访问非常频繁的热点数据请求，无法在缓存中进行处理，紧接着访问该数据的大量请求，一下子都发送到后端数据库，导致数据库压力激增影响数据库处理其他请求。</p>
<p>缓存击穿发生在热点数据过期失效时。</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302145451720.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302145451720"></p>
<p>解决方案：</p>
<p>对于访问特别频繁的热点数据，我们不再设置过期时间。<br>对于热点数据的请求访问，都可以在缓存中进行处理。</p>
<h3 id="10-3-缓存穿透"><a href="#10-3-缓存穿透" class="headerlink" title="10.3 缓存穿透"></a>10.3 缓存穿透</h3><p>缓存穿透：要访问的数据既不是Redis缓存中也不再数据库中，导致请求在访问缓存时，发生缓存缺失，再去访问数据库时，发现数据库中也没有要访问的数据。<br>如果应用持续有大量请求访问数据，就会同时给缓存和数据库带来巨大压力 </p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302145615776.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302145615776"></p>
<p><strong>产生缓存穿透的原因：</strong></p>
<ul>
<li>业务层误操作：缓存中的数据和数据库中的数据都被误删除，所以缓存和数据库中都没有数据； </li>
<li>恶意攻击：专门访问数据库中没有的数据</li>
</ul>
<p>三种解决方案：<br>1.缓存空值或者缺省值</p>
<ul>
<li> 一旦发生缓存穿透，针对查询数据在Redis缓存一个空值或者业务层协商确定的缺省值。应用发生后续请求在进行查询时，可以直接从Redis读取空值或者缺省值返回给业务应用，避免大量请求发送给数据库处理，保持数据库的正常运行。</li>
</ul>
<p>2.使用布隆过滤器快速判断数据是否存在，避免从数据库中查询数据是否存在，减轻数据库压力。</p>
<p>3.前端对非法请求校验，避免请求进入缓存或者数据库。</p>
<h2 id="11-Redis分布式锁"><a href="#11-Redis分布式锁" class="headerlink" title="11.Redis分布式锁"></a>11.Redis分布式锁</h2><h3 id="11-1-什么是Lua？"><a href="#11-1-什么是Lua？" class="headerlink" title="11.1 什么是Lua？"></a>11.1 什么是Lua？</h3><p>Lua是一种轻量小巧的脚本语言，用标准C语言编写并以源代码形式开放。<br>其设计目的就是为了嵌入应用程序中，从而为应用程序提供灵活的扩展和定制功能。因为广泛的应用于：游戏开发、独立应用脚本、Web 应用脚本、扩展和数据库插件等。<br>比如：Lua脚本用在很多游戏上，主要是Lua脚本可以嵌入到其他程序中运行，游戏升级的时候，可以直接升级脚本，而不用重新安装游戏。</p>
<p><strong>Redis中为什么引入Lua脚本？</strong><br>Redis是高性能的key-value内存数据库，在部分场景下，是对关系数据库的良好补充。<br>Redis提供了非常丰富的指令集，官网上提供了200多个命令。但是某些特定领域，需要扩充若干指令原子性执行时，仅使用原生命令便无法完成。<br>Redis 为这样的用户场景提供了 lua 脚本支持，用户可以向服务器发送 lua 脚本来执行自定义动作，获取脚本的响应数据。Redis 服务器会单线程原子性执行 lua 脚本，保证 lua 脚本在处理的过程中不会被任意其它请求打断。<br>Redis意识到上述问题后，在2.6版本推出了 lua 脚本功能，允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:</p>
<ul>
<li>减少网络开销。可以将多个请求通过脚本的形式一次发送，减少网络时延。</li>
<li>原子操作。Redis会将整个脚本作为一个整体执行，中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件，无需使用事务。</li>
<li>复用。客户端发送的脚本会永久存在redis中，这样其他客户端可以复用这一脚本，而不需要使用代码完成相同的逻辑。</li>
</ul>
<h3 id="11-2-Redis中Lua的常用命令"><a href="#11-2-Redis中Lua的常用命令" class="headerlink" title="11.2 Redis中Lua的常用命令"></a>11.2 Redis中Lua的常用命令</h3><p>命令不多，就下面这几个：</p>
<ul>
<li>EVAL</li>
<li>EVALSHA</li>
<li>SCRIPT LOAD - SCRIPT EXISTS</li>
<li>SCRIPT FLUSH</li>
<li>SCRIPT KILL</li>
</ul>
<p> <strong>EVAL命令</strong><br>命令格式：EVAL script numkeys key [key …] arg [arg …]</p>
<ul>
<li>script参数是一段 Lua5.1 脚本程序。脚本不必(也不应该[^1])定义为一个 Lua 函数</li>
<li>numkeys指定后续参数有几个key，即：key [key …]中key的个数。如没有key，则为0</li>
<li>key [key …] 从 EVAL 的第三个参数开始算起，表示在脚本中所用到的那些 Redis 键(key)。在Lua脚本中通过KEYS[1], KEYS[2]获取。</li>
<li>arg [arg …] 附加参数。在Lua脚本中通过ARGV[1],ARGV[2]获取。</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs redis">// 例1：numkeys=1，keys数组只有1个元素key1，arg数组无元素<br>127.0.0.1:6379&gt; EVAL &quot;return KEYS[1]&quot; 1 key1<br>&quot;key1&quot;<br><br>// 例2：numkeys=0，keys数组无元素，arg数组元素中有1个元素value1<br>127.0.0.1:6379&gt; EVAL &quot;return ARGV[1]&quot; 0 value1<br>&quot;value1&quot;<br><br>// 例3：numkeys=2，keys数组有两个元素key1和key2，arg数组元素中有两个元素first和second <br>//      其实&#123;KEYS[1],KEYS[2],ARGV[1],ARGV[2]&#125;表示的是Lua语法中“使用默认索引”的table表，<br>//      相当于java中的map中存放四条数据。Key分别为：1、2、3、4，而对应的value才是：KEYS[1]、KEYS[2]、ARGV[1]、ARGV[2]<br>//      举此例子仅为说明eval命令中参数的如何使用。项目中编写Lua脚本最好遵从key、arg的规范。<br>127.0.0.1:6379&gt; eval &quot;return &#123;KEYS[1],KEYS[2],ARGV[1],ARGV[2]&#125;&quot; 2 key1 key2 first second <br>1) &quot;key1&quot;<br>2) &quot;key2&quot;<br>3) &quot;first&quot;<br>4) &quot;second&quot;<br><br><br>// 例4：使用了redis为lua内置的redis.call函数<br>//      脚本内容为：先执行SET命令，在执行EXPIRE命令<br>//      numkeys=1，keys数组有一个元素userAge（代表redis的key）<br>//      arg数组元素中有两个元素：10（代表userAge对应的value）和60（代表redis的存活时间）<br>127.0.0.1:6379&gt; EVAL &quot;redis.call(&#x27;SET&#x27;, KEYS[1], ARGV[1]);redis.call(&#x27;EXPIRE&#x27;, KEYS[1], ARGV[2]); return 1;&quot; 1 userAge 10 60<br>(integer) 1<br>127.0.0.1:6379&gt; get userAge<br>&quot;10&quot;<br>127.0.0.1:6379&gt; ttl userAge<br>(integer) 44<br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105126253.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303105126253"></p>
<p><strong>SCRIPT LOAD命令 和 EVALSHA命令</strong></p>
<p>CRIPT LOAD命令格式：</p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs applescript">SCRIPT LOAD <span class="hljs-keyword">script</span><br></code></pre></td></tr></table></figure>

<p>EVALSHA命令格式：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">EVALSHA sha1 numkeys key <span class="hljs-selector-attr">[key …]</span> arg <span class="hljs-selector-attr">[arg …]</span><br></code></pre></td></tr></table></figure>

<p>这两个命令放在一起讲的原因是：EVALSHA 命令中的sha1参数，就是SCRIPT LOAD 命令执行的结果。</p>
<p>SCRIPT LOAD 将脚本 script 添加到Redis服务器的脚本缓存中，并不立即执行这个脚本，而是会立即对输入的脚本进行求值。并返回给定脚本的 SHA1 校验和。如果给定的脚本已经在缓存里面了，那么不执行任何操作。</p>
<p>在脚本被加入到缓存之后，在任何客户端通过EVALSHA命令，可以使用脚本的 SHA1 校验和来调用这个脚本。脚本可以在缓存中保留无限长的时间，直到执行SCRIPT FLUSH为止。</p>
<figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs accesslog"># SCRIPT LOAD加载脚本，并得到sha1值<br><span class="hljs-number">127.0.0.1:6379</span>&gt; SCRIPT LOAD <span class="hljs-string">&quot;redis.call(&#x27;SET&#x27;, KEYS[1], ARGV[1]);redis.call(&#x27;EXPIRE&#x27;, KEYS[1], ARGV[2]); return 1;&quot;</span><br><span class="hljs-string">&quot;6aeea4b3e96171ef835a78178fceadf1a5dbe345&quot;</span><br><br>## EVALSHA使用sha1值，并拼装和EVAL类似的numkeys和key数组、arg数组，调用脚本。<br><span class="hljs-number">127.0.0.1:6379</span>&gt; EVALSHA 6aeea4b3e96171ef835a78178fceadf1a5dbe345 <span class="hljs-number">1</span> userAge <span class="hljs-number">10</span> <span class="hljs-number">60</span><br>(integer) <span class="hljs-number">1</span><br><span class="hljs-number">127.0.0.1:6379</span>&gt; get userAge<br><span class="hljs-string">&quot;10&quot;</span><br><span class="hljs-number">127.0.0.1:6379</span>&gt; ttl userAge<br>(integer) <span class="hljs-number">43</span><br><br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105251166.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303105251166"></p>
<p><strong>SCRIPT EXISTS 命令</strong></p>
<p>命令格式：<code>SCRIPT EXISTS sha1 [sha1 …]</code><br>作用：给定一个或多个脚本的 SHA1 校验和，返回一个包含 0 和 1 的列表，表示校验和所指定的脚本是否已经被保存在缓存当中</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT EXISTS <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe345<br><span class="hljs-attribute">1</span>) (integer) <span class="hljs-number">1</span><br><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT EXISTS <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe346<br><span class="hljs-attribute">1</span>) (integer) <span class="hljs-number">0</span><br><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT EXISTS <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe345 <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe366<br><span class="hljs-attribute">1</span>) (integer) <span class="hljs-number">1</span><br><span class="hljs-attribute">2</span>) (integer) <span class="hljs-number">0</span><br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105333747.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303105333747"></p>
<p><strong>SCRIPT FLUSH 命令</strong></p>
<p>命令格式：SCRIPT FLUSH<br>作用：清除Redis服务端所有 Lua 脚本缓存</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT EXISTS <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe345<br><span class="hljs-attribute">1</span>) (integer) <span class="hljs-number">1</span><br><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT FLUSH<br><span class="hljs-attribute">OK</span><br><span class="hljs-attribute">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span>:<span class="hljs-number">6379</span>&gt; SCRIPT EXISTS <span class="hljs-number">6</span>aeea4b3e96171ef835a78178fceadf1a5dbe345<br><span class="hljs-attribute">1</span>) (integer) <span class="hljs-number">0</span><br><br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303105404896.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303105404896"></p>
<p><strong>SCRIPT KILL 命令</strong></p>
<p>命令格式：<code>SCRIPT KILL</code><br>作用：杀死当前正在运行的 Lua 脚本，当且仅当这个脚本没有执行过任何写操作时，这个命令才生效。 这个命令主要用于终止运行时间过长的脚本，比如一个因为 BUG 而发生无限 loop 的脚本，诸如此类。</p>
<p>假如当前正在运行的脚本已经执行过写操作，那么即使执行SCRIPT KILL，也无法将它杀死，因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下，唯一可行的办法是使用SHUTDOWN NOSAVE命令，通过停止整个 Redis 进程来停止脚本的运行，并防止不完整(half-written)的信息被写入数据库中。</p>
<h3 id="11-3-Redis执行Lua脚本文件"><a href="#11-3-Redis执行Lua脚本文件" class="headerlink" title="11.3 Redis执行Lua脚本文件"></a>11.3 Redis执行Lua脚本文件</h3><p>上面介绍的命令，是在redis客户端中使用命令进行操作。下面介绍的是直接执行 Lua 的脚本文件。</p>
<p>1.<strong>编写Lua脚本文件</strong></p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs pgsql"><span class="hljs-keyword">local</span> key = KEYS[<span class="hljs-number">1</span>]<br><span class="hljs-keyword">local</span> val = redis.<span class="hljs-keyword">call</span>(&quot;GET&quot;, key);<br><br><span class="hljs-keyword">if</span> val == ARGV[<span class="hljs-number">1</span>]<br><span class="hljs-keyword">then</span><br>        redis.<span class="hljs-keyword">call</span>(<span class="hljs-string">&#x27;SET&#x27;</span>, KEYS[<span class="hljs-number">1</span>], ARGV[<span class="hljs-number">2</span>])<br>        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span><br><span class="hljs-keyword">else</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span><br><span class="hljs-keyword">end</span><br></code></pre></td></tr></table></figure>

<p>2.<strong>执行Lua脚本文件</strong></p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs stylus">执行命令： redis-cli -<span class="hljs-selector-tag">a</span> 密码 <span class="hljs-attr">--eval</span> Lua脚本路径 key <span class="hljs-selector-attr">[key …]</span> ,  arg <span class="hljs-selector-attr">[arg …]</span> <br>如：redis-cli -<span class="hljs-selector-tag">a</span> <span class="hljs-number">123456</span> <span class="hljs-attr">--eval</span> ./Redis_CompareAndSet<span class="hljs-selector-class">.lua</span> userName , zhangsan lisi <br></code></pre></td></tr></table></figure>

<p>注意：”–eval”而不是命令模式中的”eval”，一定要有前端的两个-<br>脚本路径后紧跟key [key …]，相比命令行模式，少了numkeys这个key数量值<br>key [key …] 和 arg [arg …] 之间的“ , ”，英文逗号前后必须有空格，否则报错</p>
<p>演示：</p>
<p>lua脚本文件</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303102957857.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303102957857"></p>
<p>win下启动redis服务端和客户端</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cmd">redis-server.exe redis.windows.conf<br><br>redis-cli.exe -h <span class="hljs-number">127</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span>.<span class="hljs-number">1</span> -p <span class="hljs-number">6379</span><br></code></pre></td></tr></table></figure>

<p>客户端中存入name</p>
<figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs clean">## Redis客户端执行<br><span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">6379</span>&gt; set userName zhangsan <br>OK<br><span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">6379</span>&gt; get userName<br><span class="hljs-string">&quot;zhangsan&quot;</span><br><br>###############################<br>## linux服务器执行<br>## 第一次执行：compareAndSet成功，返回<span class="hljs-number">1</span><br>## 第二次执行：compareAndSet失败，返回<span class="hljs-number">0</span><br>[root@vm01 learn_lua]# redis-cli -a <span class="hljs-number">123456</span> --eval Redis_CompareAndSet.lua userName , zhangsan lisi<br>(integer) <span class="hljs-number">1</span><br>[root@vm01 learn_lua]# redis-cli -a <span class="hljs-number">123456</span> --eval Redis_CompareAndSet.lua userName , zhangsan lisi<br><br>(integer) <span class="hljs-number">0</span><br><br>##################win<br></code></pre></td></tr></table></figure>

<p>在redis目录下启动cmd，并执行lua脚本文件</p>
<figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">redis-cli.exe -a <span class="hljs-number">123456</span> --eval Redis_CompareAndSet.lua userName , zhangsan lisi<br></code></pre></td></tr></table></figure>

<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230303103730514.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230303103730514"></p>
<h3 id="11-4-分布式锁概览"><a href="#11-4-分布式锁概览" class="headerlink" title="11.4 分布式锁概览"></a>11.4 分布式锁概览</h3><p>在多线程的环境下，为了保证一个代码块在同一时间只能由一个线程访问，Java中我们一般可以使用synchronized语法和ReetrantLock去保证，这实际上是本地锁的方式。但是现在公司都是流行分布式架构，在分布式环境下，如何保证不同节点的线程同步执行呢？</p>
<p>实际上，对于分布式场景，我们可以使用分布式锁，它是控制分布式系统之间互斥访问共享资源的一种方式。</p>
<p>比如说在一个分布式系统中，多台机器上部署了多个服务，当客户端一个用户发起一个数据插入请求时，如果没有分布式锁机制保证，那么那多台机器上的多个服务可能进行并发插入操作，导致数据重复插入，对于某些不允许有多余数据的业务来说，这就会造成问题。而分布式锁机制就是为了解决类似这类问题，保证多个服务之间互斥的访问共享资源，如果一个服务抢占了分布式锁，其他服务没获取到锁，就不进行后续操作。大致意思如下图所示：</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/v2-d18f48c7c7ae783f042dd7b00ed6be99_r.jpg" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="img"></p>
<p><strong>分布式锁的特点</strong></p>
<p>分布式锁一般有如下的特点：</p>
<ul>
<li>互斥性： 同一时刻只能有一个线程持有锁</li>
<li>可重入性： 同一节点上的同一个线程如果获取了锁之后能够再次获取锁</li>
<li>锁超时：和J.U.C中的锁一样支持锁超时，防止死锁</li>
<li>高性能和高可用： 加锁和解锁需要高效，同时也需要保证高可用，防止分布式锁失效</li>
<li>具备阻塞和非阻塞性：能够及时从阻塞状态中被唤醒</li>
</ul>
<p><strong>分布式锁的实现方式</strong></p>
<p>我们一般实现分布式锁有以下几种方式：</p>
<ul>
<li><p>基于数据库</p>
</li>
<li><p>基于Redis</p>
</li>
<li><p>基于zookeeper</p>
</li>
</ul>
<h3 id="11-5-Redis的分布式锁实现"><a href="#11-5-Redis的分布式锁实现" class="headerlink" title="11.5 Redis的分布式锁实现"></a>11.5 Redis的分布式锁实现</h3><p>1.利用setnx+expire命令 </p>
<p>Redis的SETNX命令，setnx key value，将key设置为value，当键不存在时，才能成功，若键存在，什么也不做，成功返回1，失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写</p>
<p>因为分布式锁还需要超时机制，所以我们利用expire命令来设置，实际上步骤是有些问题的，setnx和expire是分开的两步操作，不具有原子性，如果执行完第一条指令应用异常或者重启了，锁将无法过期。</p>
<p>一种改善方案就是使用Lua脚本来保证原子性（包含setnx和expire两条指令）</p>
<p><strong>lua脚本为什么可以保证原子性：</strong></p>
<p>简单来说，因为所有的lua脚本在Redis实例中共用同一个Lua解释器，某一个lua脚本在被执行的时候，其他lua脚本无法执行。</p>
<p>因此对于其他lua脚本而言，一个lua脚本要么不可见，要么就已经执行完了，而这恰恰是原子性的定义。</p>
<p><strong>2.使用 set key value [EX seconds] [PX milliseconds] [NX|XX] 命令 (正确做法)</strong></p>
<p>Redis在 2.6.12 版本开始，为 SET 命令增加一系列选项：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs redis">set key value [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|keepttl] [nx|xx] [get]<br></code></pre></td></tr></table></figure>

<p>说明：设置string类型的键值，如果key已经保存了一个值，那么这个操作会直接覆盖原来的值，并且忽略原始类型<br>    key：存储的key名称<br>    value：存储的value数据<br>    [ex seconds|px milliseconds|exat timestamp|pxat milliseconds-timestamp|keepttl]：<br>        ex seconds：设置键key的过期时间，单位时秒<br>        px milliseconds：设置键key的过期时间，单位时毫秒<br>        exat timestamp：设置键过期的指定Unix时间，以秒为单位<br>        pxat milliseconds-timestamp：设置键过期的指定Unix时间，以毫秒为单位<br>        keepttl：获取key的过期时间<br>    [nx|xx]：<br>        nx：只有键key不存在的时候才会设置key的值，就等同于setnx命令<br>        xx：只有键key存在的时候才会设置key的值<br>    [get]：返回 key 存储的值，如果 key 不存在返回空</p>
<p>但是还是会产生问题：</p>
<p>删除key，有肯能删除key的时候key已经失效了，因此在执行删除的时候实际删除的是其他的线程或者进程获取到的锁。</p>
<p>解决办法：</p>
<p>可以设置value为一组随机数字或者字符串，解锁的时候进行比较如果和设值时候一样则删除，但理论上还是存在误删其他线程或者进程的锁的情况，因为查和删非原子操作。所以，一般来说释放锁通过lua脚本来进行。因为Redis在执行Lua脚本的过程中，其他的客户端命令都需要等待该Lua脚本执行完才能执行。</p>
<p>value必须要具有唯一性，可以用UUID来做，设置随机字符串保证唯一性，至于为什么要保证唯一性？假如value不是随机字符串，而是一个固定值，那么就可能存在下面的问题：</p>
<ul>
<li>1.客户端1获取锁成功</li>
<li>2.客户端1在某个操作上阻塞了太长时间</li>
<li>3.设置的key过期了，锁自动释放了</li>
<li>4.客户端2获取到了对应同一个资源的锁</li>
<li>5.客户端1从阻塞中恢复过来，因为value值一样，所以执行释放锁操作时就会释放掉客户端2持有的锁，这样就会造成问题</li>
</ul>
<p>所以通常来说，在释放锁时，我们需要对value进行验证。</p>
<h2 id="12-主从复制"><a href="#12-主从复制" class="headerlink" title="12. 主从复制"></a>12. 主从复制</h2><h3 id="12-1-什么是主从复制"><a href="#12-1-什么是主从复制" class="headerlink" title="12.1 什么是主从复制"></a>12.1 什么是主从复制</h3><ul>
<li>主从复制指将一台Redis服务器(master)的数据其他的Redis服务器(slave)的过程。数据的复制是单向的，只能由主节点到从节点。</li>
<li>默认情况下，每台Redis服务器都是主节点；且一个主节点可以有多个从节点(或没有从节点)，但一个从节点只能有一个主节点。</li>
<li>Redis 持久化将数据写入磁盘中， redis 服务重启后会将硬盘上持久化的数据恢复到内存中，但是当 redis 服务器的硬盘损坏可能会导致数据丢失，如果通过 redis 的主从复制机制就可以避免这种单点故障。</li>
</ul>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="redis主从复制"></p>
<p>图解说明：</p>
<ul>
<li><p>主 Redis 中的数据有两个副本（replication）即从Redis 1 和从Redis 2，即使一台 Redis 服务器宕机其它两台 Redis 服务也可以继续提供服务。</p>
</li>
<li><p>主 Redis 中的数据和从 redis 上的数据保持同步，当主 Redis 写入数据时通过主从复制机制会复制到两个从 Redis 服务上。</p>
</li>
<li><p>一个主 Redis 可以有多个从 Redis 。</p>
</li>
<li><p>主从复制不会阻塞 master，在同步数据时，master 可以继续处理 client 请求。</p>
</li>
</ul>
<p><strong>主从复制的作用</strong></p>
<ul>
<li>数据备份：主从复制实现了数据的热备份，是持久化（RDB/AOF）之外的一种数据备份方式。</li>
<li>故障恢复：当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复。</li>
<li>负载均衡：主从复制配合读写分离，可以由主节点提供写服务，由从节点提供读服务，分担服务器负载；在写少读多的场景下，通过多个从节点分担读负载，可以提高Redis服务器的并发量。</li>
</ul>
<h3 id="12-2-主从复制的实现过程"><a href="#12-2-主从复制的实现过程" class="headerlink" title="12.2 主从复制的实现过程"></a>12.2 主从复制的实现过程</h3><ol>
<li>当启动一个 slave node 的时候，它会发送一个 PSYNC 命令给 master node。</li>
<li>如果这是 slave node 初次连接到 master node，那么会触发一次 full resynchronization 全量复制。</li>
<li>此时 master 会启动一个后台线程，开始生成一份 RDB 快照文件，</li>
<li>同时还会将从客户端 client 新收到的所有写命令缓存在内存中。</li>
<li>RDB 文件生成完毕后， master 会将这个 RDB 发送给 slave，slave 会先写入本地磁盘，然后再从本地磁盘加载到内存中，接着 master 会将内存中缓存的写命令发送到 slave，slave 也会同步这些数据。</li>
<li>slave node 如果跟 master node 有网络故障，断开了连接，会自动重连，连接之后 master node 仅会复制给 slave 部分缺少的数据.</li>
</ol>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E8%BF%87%E7%A8%8B.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="redis主从复制过程"></p>
<p><strong>全量复制和增量复制</strong><br>从节点发送psync命令请求同步数据，此时根据主从节点当前状态的不同，同步方式可能是全量复制或增量复制。</p>
<ul>
<li>全量复制：用于初次复制或其他无法进行增量复制的情况，将主节点中的所有数据都发送给从节点，是一个非常重型的操作。</li>
<li>增量复制：用于网络中断等情况后的复制，只将中断期间主节点执行的写命令发送给从节点，与全量复制相比更加高效。如果网络中断时间过长，导致主节点没有能够完整地保存中断期间执行的写命令，则无法进行部分复制，仍使用全量复制。</li>
</ul>
<p>主从复制分为三个阶段：</p>
<ul>
<li><strong>连接建立阶段</strong></li>
<li><strong>数据同步阶段</strong></li>
<li><strong>命令传播阶段</strong></li>
</ul>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/redis%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E8%BF%9E%E6%8E%A5%E9%98%B6%E6%AE%B5.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="redis主从复制连接阶段"></p>
<p>(1) <strong>连接建立阶段主要作用是在主从节点之间建立连接，为数据同步做好准备。</strong></p>
<p>主要分为：保存主节点信息，建立socket连接，发送ping命令，身份验证，发送从节点端口信息5个步骤。</p>
<ol>
<li>保存主节点信息</li>
</ol>
<p>Slave服务器内部维护了masterhost和masterport字段，用于存储主节点的ip和port信息。</p>
<ol start="2">
<li>建立socket连接</li>
</ol>
<p>从节点每秒调用一次复制定时函数replicationCron()，根据主节点的ip和port，创建socket连接。</p>
<p>连接成功后</p>
<ul>
<li>从节点：给该socket建立一个专门处理复制工作的文件事件处理器，负责后续的复制工作，如接收RDB文件、接收命令传播等。</li>
<li>主节点：接收到从节点的socket连接后（即accept之后），为该socket创建相应的客户端状态，并将从节点看做是连接到主节点的一个客户端。</li>
</ul>
<ol start="3">
<li>发送ping命令</li>
</ol>
<p>建立socket连接后从节点会给主节点发送ping命令，检查socket连接是否可用及主节点是否能够处理请求。</p>
<ol start="4">
<li>身份验证</li>
</ol>
<p>若从节点中设置了masterauth选项，则从节点需要向主节点进行身份验证，没有设置该选项，则不需要验证，默认是关闭的。</p>
<ol start="5">
<li>发送从节点端口信息</li>
</ol>
<p>身份验证之后，从节点会向主节点发送其监听的端口号，主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中；该端口信息除了在主节点中执行info Replication时显示以外，没有其他作用。</p>
<p>(2) <strong>数据同步阶段</strong><br>主从节点之间的连接建立以后，便可以开始进行数据同步，该阶段可以理解为从节点数据的初始化。具体执行的方式是：从节点向主节点发送psync命令开始同步。</p>
<p>数据同步阶段根据主从节点当前状态的不同，可以分为全量复制和部分复制</p>
<p>数据同步阶段之前，从节点是主节点的客户端，主节点不是从节点的客户端；<br>原因：在数据同步阶段之前，主节点只需要响应从节点的请求即可，不需要主动发请求，</p>
<p>数据同步阶段以及之后阶段，主从节点互为客户端。<br>原因：在数据同步阶段和后面的命令传播阶段，主节点需要主动向从节点发送请求完成复制（如推送缓冲区中的写命令）。</p>
<p>（3） 命令传播阶段</p>
<p>数据同步阶段完成后，主从节点进入命令传播阶段；在这个阶段主节点将自己执行的写命令发送给从节点，从节点接收命令并执行，从而保证主从节点数据的一致性。</p>
<p>Redis 的主从复制模式利用master来处理写操作，slave提供读操作，这样可以有效减少单个机器的并发访问数量。</p>
<p>要实现主从复制这种模式，主节点不用做任何修改，直接启动服务即可。从节点需要修改redis.conf配置文件，加入配置：slaveof &lt;主节点ip地址&gt; &lt;主节点端口号&gt;，例如master的ip地址为192.168.200.129，端口号为6379，那么slave只需要在redis.conf文件中配置slaveof 192.168.200.129 6379即可。<br>分别连接主节点和从节点，测试发现主节点的写操作，从节点立刻就能看到相同的数据。但是在从节点进行写操作，提示 READONLY You can’t write against a read only slave 不能写数据到从节点。</p>
<p><img src="https://raw.githubusercontent.com/hellelixi/note-image01/main/images/image-20230302164349034.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302164349034"></p>
<p>演示：win下从配置</p>
<p><img src="%E5%9B%BE%E5%BA%93/image-20230302164441950.png" srcset="/hello_lixi.gitee.io/img/loading.gif" lazyload alt="image-20230302164441950"></p>
<p>master主要配置</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs conf"># 开启无盘复制<br>repl-diskless-sync yes<br># 无盘复制的延迟默认为5s，是为了等待更多的slave连接<br>repl-diskless-sync-delay 5<br># 是否开启主从节点复制数据的延迟机制<br># 当关闭时，主节点产生的命令数据无论大小都会及时地发送给从节点，这样主从之间延迟会变小<br># 但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景<br># 当开启时，主节点会合并较小的TCP数据包从而节省带宽。<br># 默认发送时间间隔取决于Linux的内核，一般默认为40毫秒。<br># 这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景<br>repl-disable-tcp-nodelay no<br># 主从节点进行心跳的时间间隔<br>repl-ping-slave-period 10<br><br></code></pre></td></tr></table></figure>

<p>slaver主要配置</p>
<figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs livecodeserver"><span class="hljs-comment"># 绑定master的ip和port</span><br>slaveof <span class="hljs-number">100.100</span><span class="hljs-number">.100</span><span class="hljs-number">.100</span> <span class="hljs-number">6379</span><br><span class="hljs-comment"># 从节点只读</span><br>slave-<span class="hljs-built_in">read</span>-only yes<br><span class="hljs-comment"># 从节点在处于快照同步期间是否对外提供服务</span><br>slave-serve-stale-data yes<br><span class="hljs-comment"># 如果 master 检测到 slave 的数量小于这个配置设置的值，将拒绝对外提供服务，0 代表，无论 slave 有几个都会对外提供服务</span><br><span class="hljs-built_in">min</span>-slaves-<span class="hljs-built_in">to</span>-<span class="hljs-built_in">write</span> <span class="hljs-number">0</span><br><span class="hljs-comment"># 如果 master 发现大于等于 $&#123;min-slaves-to-write&#125; 个 slave 与自己的心跳超过此处配置的时间（单位s）</span><br><span class="hljs-comment"># 就拒绝对外提供服务</span><br><span class="hljs-built_in">min</span>-slaves-<span class="hljs-built_in">max</span>-lag <span class="hljs-number">10</span><br><span class="hljs-comment"># master的认证口令</span><br>masterauth redis<br></code></pre></td></tr></table></figure>





<p>哨兵模式</p>
<p>Redis集群</p>
<p>Redis 5.0以后的版本，集群管理软件 redis-trib.rb 被集成到 redis-cli 客户端工具中</p>
<p>本文参考网络各技术博客完成，部分图片来自网络，仅供自己学使用</p>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/hello_lixi.gitee.io/categories/java%E5%BC%80%E5%8F%91/" class="category-chain-item">java开发</a>
  
  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/hello_lixi.gitee.io/tags/Redis/">#Redis</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>Redis学习</div>
      <div>https://xistudycode.gitee.io/hello_lixi.gitee.io/2023/04/11/Redis学习笔记/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>Mr.LI</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2023年4月11日</div>
        </div>
      
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/hello_lixi.gitee.io/2023/05/11/Spring%E5%BC%80%E5%8F%91%E4%B8%AD%E9%81%87%E5%88%B0%E7%9A%84redis%E9%97%AE%E9%A2%98/" title="SpringBoot开发中遇到的redis问题">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">SpringBoot开发中遇到的redis问题</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/hello_lixi.gitee.io/2023/04/10/%E8%B7%A8%E5%9F%9F%E5%A4%84%E7%90%86/" title="跨域处理问题">
                        <span class="hidden-mobile">跨域处理问题</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>xBolg</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>@Fluid</span></a> 
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="busuanzi_container_site_pv" style="display: none">
        总访问量 
        <span id="busuanzi_value_site_pv"></span>
         次
      </span>
    
    
      <span id="busuanzi_container_site_uv" style="display: none">
        总访客数 
        <span id="busuanzi_value_site_uv"></span>
         人
      </span>
    
    
  
</div>

  
  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/hello_lixi.gitee.io/js/events.js" ></script>
<script  src="/hello_lixi.gitee.io/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/hello_lixi.gitee.io/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/hello_lixi.gitee.io/js/local-search.js" ></script>

  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/hello_lixi.gitee.io/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
